Хотел перевести своего бота на английский язык, но в интернете нашел только эту статью. Информация взята с:
https://robotvasya.github.io/i18n-l10n-tutorial-aiogram/chapter_04.html

Если вы не знаете основы Aiogram3, router, клавиатуры то вам сюда:
https://mastergroosha.github.io/aiogram-3-guide/
Остальные поехали!
Устанавливаем библиотеки в ваш проект

pip install aiogram_i18n
pip install fluent_compiler
pip install fluent.runtime

Этап 1
Создаем главный файл bot.py с одним хэндлером start. Начинаем с малого.
Разбор кода сразу после. Поехали!

import asyncio
import logging
from aiogram import Bot, Dispatcher, types
from aiogram.filters.command import Command
from aiogram_i18n import I18nContext
from aiogram_i18n import I18nMiddleware
from aiogram_i18n.cores.fluent_runtime_core import FluentRuntimeCore

# Включаем логирование, чтобы не пропустить важные сообщения
logging.basicConfig(level=logging.INFO)
# Объект бота
bot = Bot(token="Ваш токен")
# Диспетчер
dp = Dispatcher()

# Хэндлер на команду /start
@dp.message(Command("start"))
async def cmd_start(message: types.Message, i18n: I18nContext):
    await message.answer(i18n.get("Hello"))

# Запуск процесса поллинга новых апдейтов
async def main():
    i18n_middleware = I18nMiddleware(
        core=FluentRuntimeCore(
            path="locales/{locale}/LC_MESSAGES",
        ),
        default_locale="ru")
    i18n_middleware.setup(dispatcher=dp)
    # пропускаем все накопленные входящие
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())

Строка 5-7: импортируем нужные библиотеки.
Строка 17-19 (хэндлер start): передаем новую переменную в функцию (i18n: I18nContext) и вместо поля text пишем (i18n.get("Hello"). Fluent будет искать значение переменной "Hello" в папках локали, которые мы создадим позже.
Строка 23-28: подключаем движок перевода: назначаем путь до папки локали, задаем локаль по умолчанию, подключаем к диспетчеру.

Создаем папку локалей (ru, en), как на картинке внизу (locales - en - LC_MESSAGES - my_bot.ftl)

Открываем файл my_bot.ftl (назвать можно как угодно, главное чтобы формат был ftl) для русской локали и записываем:

Hello = Привет! Это бот для проверки смены языка.

Без кавычек и как напишите, так и увидите. Также для en локали.

Hello = Hello! This is a bot for checking language changes.

Попробуйте запустить бота (меняя в функции main переменную default_locale меняется и язык).
Если всё работает, переходим к этапу 2.

Этап 2

Сделаем команду /language, которая выводит клавиатуру с выбором языка. И после выбора пользователя, меняется язык всего приложения (бота).
Создаем файл choice_language.py в папке handlers (это больше для чистоты приложения, можете где хотите создать файл).

from aiogram import types, F, Router
from aiogram.filters.command import Command
from aiogram_i18n import I18nContext

router = Router()

#клавиатура с выбором языка
keyboard = types.ReplyKeyboardMarkup(
    keyboard=[
        [types.KeyboardButton(text="English")],
        [types.KeyboardButton(text="Русский")]
    ], resize_keyboard=True)

@router.message(Command("language"))
async def cmd_start(message: types.Message):
    await message.answer("Выбери язык (choice language):", reply_markup=keyboard)


@router.message(F.text == "English")
async def switch_to_en(message: types.Message, i18n: I18nContext):
    await switch_language(message, i18n,"en")


@router.message(F.text == "Русский")
async def switch_to_en(message: types.Message, i18n: I18nContext):
    await switch_language(message, i18n,"ru")

async def switch_language(message: types.Message, i18n: I18nContext, locale_code: str):
    await i18n.set_locale(locale_code)
    await message.answer(i18n.get("lang-is-switched"), reply_markup=types.ReplyKeyboardRemove())

Не забудьте подключить роутер к диспетчеру. Мы создаем клавиатуру с выбором языка.
Дальше создаем команду /language, которая выводит клавиатуру.
Дальше хэндлер принимает ввод
(если выбран English, то будет выбран хэндлер с F.text == "English") и вызывает функцию switch_language с аргументом locale_code (если English, то передается "en").
И в строке 29 меняется locale_code.

Остался последний вопрос: что делать с клавиатурами?
В файле choice_language.py мы создали клавиатуру с заранее известным текстом. Теперь попробуем создать клавиатуру с текстом, который берется с локали.

Импортируем модули:

from aiogram_i18n import LazyProxy
from aiogram_i18n.types import (
    ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardButton, InlineKeyboardMarkup
    # you should import mutable objects from here if you want to use LazyProxy in them
)

То есть клавиатуру создаем через модуль aiogram_18n.

def main_keyboard():
    buttons = [
        [KeyboardButton(text=LazyProxy('donat_button'))],
        [KeyboardButton(text=LazyProxy('support_button'))]
    ]
    keyboard = ReplyKeyboardMarkup(keyboard=buttons, resize_keyboard=True)
    return keyboard

Также в файлах перевода переведите данные переменные
(support_button = Группа тех. поддержки...).

Еще можно прокидывать переменные в перевод. Например:

await message.answer(text=i18n.get("you-have-coin", value=n)
you-have-coin = У пользователя { $user } { $value ->
        [0] совсем нет монет
        [one] имеется одна монета
        [few] { $value } монеты
        *[many] есть { $value } монет
    }!

От значения value будет выбран нужный селектор
(если value = 1, то вывод: "имеется одна монета")

И конечно ссылка на репозиторий:

https://github.com/TheStupidBear/Aiogram_fluent_tutorial

Спасибо за внимание.