Перейти к содержимому
Кодировка текста и «кракозябры»: почему ломается текст и как починить
blogQRcode.website26218 мин чтения

Кодировка текста и «кракозябры»: почему ломается текст и как починить

#кодировка текста#кракозябры#UTF-8#Windows-1251#CSV#Excel#Unicode#BOM#mojibake

Вы открываете файл, а вместо «Договор №14» видите «Р”РѕРіРѕРІРѕСЂ в„–14». Или выгружаете заказы из CRM, открываете CSV в Excel — и вся русская колонка превращается в «Ïðèâåò». Текст при этом не потерян: байты в файле те же, что и были. Испорчен договор о том, как эти байты читать.

Кракозябры (в англоязычной литературе — mojibake) возникают ровно в одном случае: программа прочитала байты в одной кодировке, а записаны они были в другой. Ниже — как опознать тип поломки по внешнему виду, как вернуть текст и как настроить систему, чтобы это не повторялось: в файлах, CSV, браузере, письмах, консоли Windows и внутри QR-кодов.

Кодировка простыми словами: байты против символов

Файл на диске не хранит буквы. Он хранит числа от 0 до 255 — байты. Кодировка (charset, encoding) — таблица соответствия: какое число какому символу отвечает. Байт 0x41 — это «A» по таблице ASCII и остаётся «A» почти везде. А вот байт 0xCF — «П» в Windows-1251, «Ï» в Windows-1252, «п» в CP866 и вообще невалидное начало последовательности в UTF-8.

Базовый слой — ASCII: 7 бит, 128 позиций, латиница, цифры, пунктуация и управляющие символы. Русских букв там нет, поэтому вся история национальных кодировок — это попытки пристроить кириллицу в диапазон 128–255. Разбор самой таблицы и её диапазонов — в отдельном материале про таблицу ASCII.

Однобайтовые кодировки: почему их было так много

В диапазон 128–255 помещается 128 дополнительных позиций — для русского алфавита с запасом. Но каждый производитель раскладывал буквы по-своему:

  • Windows-1251 (CP1251, ANSI) — основная кириллическая кодировка Windows, алфавит идёт подряд: «А» = 0xC0, «я» = 0xFF. Именно её выплёвывают старые бухгалтерские выгрузки и банк-клиенты.
  • CP866 (OEM, DOS) — кодировка русского DOS. Живёт в консоли Windows и в именах файлов внутри старых ZIP-архивов.
  • KOI8-R — кодировка советского и раннего российского Unix, спроектирована так, что при обнулении восьмого бита текст превращался в читаемый транслит.
  • ISO-8859-5 и MacCyrillic — стандарт ISO и кириллица классической Mac OS, обе почти не прижились.

Пять таблиц для одного алфавита, ни одна не совместима с другой выше 127-й позиции. Отсюда легендарные кракозябры девяностых.

Unicode, UTF-8 и разница между ними

Unicode — не кодировка, а реестр символов: каждому знаку присвоен номер (кодовая точка) вида U+041F для «П» или U+1F600 для смайла. UTF-8, UTF-16 и UTF-32 — уже способы записать эти номера байтами.

У самого распространённого, UTF-8, две ключевые особенности. Первая: он совместим с ASCII, латиница занимает один байт с теми же значениями. Вторая: длина переменная — символ занимает от одного до четырёх байтов, кириллица укладывается в два, эмодзи в четыре. Практическое следствие: «Привет» в Windows-1251 весит 6 байт, а в UTF-8 — 12. Байты и символы перестают быть одним и тем же, и это регулярно ломает старый код. Общая картина того, как любая информация превращается в последовательность битов, — в обзоре про кодирование информации, а про сами биты и байты — в материале про двоичный код.

Почему появляются кракозябры: механика сбоя

Корень проблемы в том, что обычный текстовый файл не хранит внутри название своей кодировки. Там просто байты. Открывая файл, редактор или Excel либо берёт кодировку из явного указания (HTTP-заголовок, атрибут в HTML, метка BOM), либо угадывает по системным настройкам и статистике. Угадывание ошибается.

Сценарий 1: UTF-8 прочитали как Windows-1251

Самый частый случай, и его легко узнать: русский текст удваивается в длине, каждая буква превращается в пару символов, где первым идёт «Р» или «С».

Механика видна на примере. «Привет» в UTF-8 — это байты D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82. Читаем те же байты по таблице Windows-1251, где 0xD0 = «Р», 0x9F = «џ», 0xD1 = «С», 0x80 = «Ђ», — получаем «РџСЂРёРІРµС‚». Поломка полностью обратима: байты целы, испорчена интерпретация.

Сценарий 2: Windows-1251 прочитали как Latin-1

Вторая классика: «Привет» превращается в «Ïðèâåò». Кодировка тоже однобайтовая, поэтому длина не меняется — каждая кириллическая буква заменяется латиницей с диакритикой: 0xCF показывается как «Ï», 0xF0 как «ð», 0xE8 как «è». Такой вид обычно означает, что в цепочке стоит система с западноевропейской локалью: зарубежный хостинг, почтовый шлюз, чужая CRM. Тоже обратимо.

Сценарий 3: однобайтовый текст прочитали как UTF-8

Здесь начинаются настоящие потери. UTF-8 — кодировка со строгой структурой: первый байт задаёт длину последовательности, продолжающие байты обязаны начинаться с битов 10. Байты Windows-1251 этой структуре, как правило, не подчиняются. Декодер натыкается на невалидную последовательность и подставляет символ замены — ромбик с вопросом «�», иногда просто «?». Вот это уже необратимо: исходный байт заменён заглушкой. Если файл в таком виде сохранили, данные потеряны. Родственный случай — кириллицу сохранили в кодировку, где её нет вовсе (ASCII, Latin-1): каждая русская буква становится «?», и это тоже необратимо.

Шпаргалка по внешнему виду

Что видно на экранеЧто произошлоОбратимо
ПриветUTF-8 читают как Windows-1251Да
ÏðèâåòWindows-1251 читают как Windows-1252 / Latin-1Да
рТЙЧЕФKOI8-R читают как Windows-1251Да
ПЁЁ┴┴ (псевдографика)CP866 читают как Windows-1251 или наоборотДа
Ð ÑƒÑ (символов вчетверо больше)Двойная перекодировка: сломанный текст сломали ещё разДа, в два прохода
������Однобайтовый текст читают как UTF-8, символы замененыНет, если сохранили
??????Кириллицу сохранили в кодировку без кириллицыНет
Текст читается, но эмодзи стали «?»База данных в utf8mb3 вместо utf8mb4Нет для уже записанных

Последняя строка — отдельная ловушка MySQL. Кодировка с названием utf8 там исторически означает урезанный вариант на три байта (utf8mb3), куда не влезают эмодзи и редкие иероглифы. Полноценный вариант называется utf8mb4, указывать нужно именно его.

UTF-8 против Windows-1251: что выбирать

Ответ короткий: UTF-8 всегда, кроме случаев, когда принимающая сторона жёстко требует иного. Но различия объясняют половину проблем интеграций.

ПараметрWindows-1251UTF-8
Размер символаВсегда 1 байт1–4 байта
Русская буква1 байт2 байта
Латиница и цифры1 байт, как в ASCII1 байт, как в ASCII
Эмодзи, иероглифыНе поддерживаютсяПоддерживаются
Смешение языков в файлеТолько кириллица и латиницаЛюбое
Проверка целостностиНевозможна: любой байт валиденВозможна: структура строгая
Доля в вебеДоли процента, только легасиПодавляющее большинство сайтов

Единственное реальное преимущество Windows-1251 — компактность на русском тексте, и на современных объёмах она несущественна. Кодировка не исчезла по другой причине: за тридцать лет вросла в корпоративный контур. Это выгрузки из старых учётных систем и типовых конфигураций 1С, файлы обмена с банками и платёжные реестры, текстовые протоколы весов и терминалов сбора данных, прайс-листы поставщиков в CSV и DBF, старые сайты, где база и шаблоны так и остались в 1251. Если контрагент присылает файл в 1251, спорить бесполезно — проще один раз настроить конвертацию на входе.

Как определить кодировку файла

Стопроцентно надёжного способа не существует, определение всегда вероятностное. Есть иерархия признаков от самых твёрдых к самым шатким.

  1. BOM в начале файла. Служебная метка: для UTF-8 это EF BB BF, для UTF-16 LE — FF FE, для UTF-16 BE — FE FF. Если метка есть, гадать не нужно. Но для UTF-8 она необязательна, и большинство современных инструментов её не ставят: BOM ломает shebang в скриптах и склейку файлов.
  2. Валидность UTF-8. Структура жёсткая, и случайный однобайтовый текст ей почти никогда не соответствует. Если файл целиком проходит проверку и в нём есть байты выше 127 — это с высокой вероятностью UTF-8. Если проверка падает — точно не UTF-8.
  3. Статистика байтов. Различить 1251, CP866 и KOI8-R по структуре нельзя, там любой байт валиден. Работают только частотные модели языка: библиотеки chardet и charset-normalizer декодируют текст всеми вариантами и выбирают самый похожий на осмысленный русский. Точность высокая на длинных текстах и никакая на строке из трёх слов.
  4. Взгляд на байты глазами. Откройте файл в шестнадцатеричном виде: русские буквы парами с первым байтом D0 или D1 — UTF-8; одиночные байты C0–FF — Windows-1251; байты вперемежку с нулями — UTF-16.

В повседневной работе хватает готовых инструментов: в Notepad++ и VS Code кодировка показана в строке состояния и переключается кликом, в Linux и macOS грубую оценку даёт file -i, точнее — uchardet.

Как починить кракозябры в файле

Главное правило, из-за незнания которого теряют данные: «интерпретировать как» и «конвертировать в» — разные операции.

  • Интерпретировать как (Reopen with Encoding, меню «Кодировки») — байты не трогаются, меняется правило чтения. Это то, что нужно для починки.
  • Конвертировать в (Convert to, Save with Encoding) — байты переписываются. Сделаете это над уже сломанным текстом — кракозябры зафиксируются навсегда.

Порядок всегда один: сначала правильно прочитать, потом сохранить в UTF-8. И работать на копии.

Notepad++ и VS Code

В Notepad++: откройте файл, посмотрите текущую кодировку в правом нижнем углу, затем меню «Кодировки» → «Кириллица» → «Windows-1251». Это интерпретация, текст должен стать читаемым сразу. Стал — возвращаемся в то же меню и выбираем «Преобразовать в UTF-8 (без BOM)», сохраняем. Не помогло — переберите в том же меню OEM 866, KOI8-R и UTF-8.

В VS Code логика та же: клик по названию кодировки в строке состояния → «Reopen with Encoding» → «Cyrillic (Windows 1251)». Убедились, что текст читаем — снова клик и «Save with Encoding» → «UTF-8».

Командная строка

Классический инструмент Unix, доступный и в Git Bash под Windows:

iconv -f CP1251 -t UTF-8 input.csv > output.csv

Флаг -f — исходная кодировка, -t — целевая. Если попадаются символы, которых нет в целевой кодировке, добавьте //TRANSLIT (заменит на похожие) или //IGNORE (выбросит).

В PowerShell 7 хватает одной строки:

Get-Content in.txt -Encoding windows-1251 | Set-Content out.txt -Encoding utf8

В PowerShell 5.1 из комплекта Windows параметр -Encoding utf8 записывает файл с BOM, а набор кодировок беднее — для точного контроля надёжнее работать через .NET с явным указанием кодовой страницы 1251.

Двойную перекодировку («Ð ÑƒÑ...») раскручивают обратной последовательностью: сначала закодировать строку в те байты, которыми её ошибочно прочитали, потом раскодировать правильно. На Python это text.encode("cp1251").decode("utf-8"), при необходимости дважды.

CSV и Excel: самый частый источник боли

Больше всего обращений с кракозябрами связано не с сайтами, а с выгрузками в CSV, которые открывают в Excel. Причин две, и обычно они бьют одновременно.

Причина 1: Excel не угадывает UTF-8 без BOM

При двойном клике по файлу .csv Excel на русской Windows читает его в системной кодировке ANSI, то есть в Windows-1251. Файл в UTF-8 без BOM превращается ⠫Привет». Решений три:

  1. Добавить BOM при генерации. Три байта EF BB BF в начале — и Excel уверенно распознаёт UTF-8. В Python это кодировка utf-8-sig при записи, в PHP — вывод "\xEF\xBB\xBF" перед данными. Самый надёжный путь, если вы контролируете выгрузку.
  2. Импортировать, а не открывать. Вкладка «Данные» → «Получить данные» → «Из текста или CSV», в мастере выбрать источник «65001: Юникод (UTF-8)». Работает всегда, но требует ручных действий.
  3. Сохранить в Windows-1251. Вариант для случая, когда получатель — старая программа, которая другого не понимает.

Причина 2: разделитель

Даже с правильной кодировкой Excel может свалить всю строку в первую ячейку. Он ориентируется на региональный разделитель списка: в русской локали это точка с запятой, а стандартный CSV использует запятую. Обходные пути: выгружать с «;» либо добавить первой строкой служебное sep=,. Учтите, что эту строку понимает именно Excel, другие программы покажут её как данные.

Когда получатель — человек, надёжнее отдавать XLSX: кодировка внутри зафиксирована как UTF-8 и никем не угадывается. Google Таблицы, в отличие от Excel, считают загружаемый CSV кодировкой UTF-8 — если файл открывается в Google и ломается в Excel, дело почти наверняка в BOM.

Кракозябры на сайте и в браузере

На веб-странице кодировка объявляется явно, и приоритет такой: HTTP-заголовок ответа сервера важнее того, что написано в HTML. Если сервер отдаёт Content-Type: text/html; charset=windows-1251, а в коде стоит <meta charset="utf-8">, победит заголовок, и страница сломается.

  • Тег <meta charset="utf-8"> ставится в самом начале <head>: спецификация требует, чтобы объявление уместилось в первый килобайт документа.
  • HTTP-заголовок сервера должен указывать ту же кодировку — настраивается в конфиге nginx, Apache или на уровне приложения.
  • Файлы шаблонов сохраняются в UTF-8 без BOM: лишний BOM в PHP-файле выводится в браузер до заголовков и ломает редиректы.
  • Кодировка соединения с базой должна совпадать с кодировкой таблиц: для MySQL это SET NAMES utf8mb4 либо параметр charset в строке подключения.
  • Таблицы и колонки — utf8mb4, а не utf8, иначе эмодзи и часть символов будут теряться.

Данные форм браузер отправляет в кодировке страницы: если страница объявлена как UTF-8, а серверный обработчик ждёт 1251, кириллица в поле «Имя» приедет кракозябрами. В адресах страниц кириллица кодируется процентными последовательностями — буква «п» в UTF-8 даёт %D0%BF, а в 1251 дала бы %EF. Отсюда битые ссылки при переезде старых сайтов на новую CMS. В современных Chrome ручной выбор кодировки страницы убрали: чужой сайт с кракозябрами на своей стороне не починить, свой чинится только на сервере.

Письма, SMS и мессенджеры

В электронной почте кодировка объявляется в заголовках MIME. Тело письма сопровождается строкой вида Content-Type: text/plain; charset=UTF-8 и указанием способа переноса — base64 или quoted-printable. Второй узнаваем: русские буквы в исходнике выглядят как цепочки =D0=9F=D1=80.

Тема письма ломается чаще тела. Заголовки почтового протокола исторически допускают только ASCII, поэтому нелатинскую тему полагается упаковывать по правилам RFC 2047 в конструкцию вида =?UTF-8?B?0J/RgNC40LLQtdGC?=. Если самописный скрипт отправляет тему сырым текстом, письмо прочитается нормально, а тема превратится в мусор — классический признак рассылки через голую функцию отправки почты без корректных заголовков.

С SMS механика другая. Стандартный семибитный алфавит GSM-7 кириллицу не содержит, и как только в сообщении появляется хотя бы одна русская буква, оператор переключается на UCS-2 — двухбайтовое представление Unicode. Ёмкость падает со 160 символов до 70, а в длинных склеенных сообщениях — со 153 до 67 символов на сегмент. Отсюда практика писать рекламные рассылки транслитом: решение чисто экономическое. Эмодзи занимает две позиции UCS-2, потому что кодируется суррогатной парой. В мессенджерах проблема почти исчезла — они работают на UTF-8 поверх собственных протоколов, и кракозябры там возникают в основном при импорте истории.

Консоль Windows, bat-файлы и архивы

Командная строка Windows унаследовала кодовые страницы DOS: в русской локали cmd.exe по умолчанию работает в CP866, и вывод UTF-8 утилиты превращается в псевдографику. Переключение — команда chcp 65001, кодовая страница UTF-8.

С bat- и cmd-файлами болезненнее: интерпретатор читает их в OEM-кодировке, и файл с русскими комментариями, сохранённый в UTF-8, выдаёт мусор, а иногда и ошибки разбора команд. Решение — сохранять bat в CP866 либо писать в них только латиницу. В Windows Terminal и PowerShell 7 проще: там UTF-8 по умолчанию.

Отдельная классика — имена файлов в ZIP. Классический ZIP хранит их в кодовой странице системы, которая создавала архив; флаг для UTF-8 в спецификации есть, но старые архиваторы его не ставят. Итог знаком всем: архив, собранный на русской Windows, распаковывается на macOS с именами вида «╨Ф╨╛╨║╤Г╨╝╨╡╨╜╤В.pdf». Лечится архиватором, умеющим задавать кодировку имён вручную (7-Zip, Keka). Обходной путь для обмена — латинские имена без пробелов.

Кодировка внутри QR-кодов и штрих-кодов

Кракозябры добираются и до штриховых символик. QR-код кодирует данные в нескольких режимах: цифровом, буквенно-цифровом (набор из 45 символов, только заглавная латиница), байтовом и режиме кандзи. Кириллица идёт только в байтовом режиме — и там начинается неоднозначность.

По стандарту ISO/IEC 18004 байтовый режим по умолчанию подразумевает ISO-8859-1, где кириллицы нет. На практике почти все генераторы пишут UTF-8 и почти все сканеры смартфонов его распознают — но «почти» здесь не пустое слово: промышленные сканеры и старые приложения могут прочитать те же байты как Latin-1 и выдать «РџСЂРёРІРµС‚». Формально снять неоднозначность позволяет механизм ECI — указатель, прямо объявляющий кодировку данных (для UTF-8 это идентификатор 26). Поддерживают его не все считыватели, поэтому на практике действуют иначе:

  • Кириллица удваивает объём. Русская буква в UTF-8 занимает два байта, поэтому русский текст помещается в QR примерно вдвое хуже латинского. Байтовый режим вмещает максимум около 2953 байт при самой низкой коррекции ошибок — порядка 1400 кириллических символов, но такой код будет плотным и потребует крупной печати.
  • Ссылка надёжнее текста. Вместо длинного русского текста лучше зашивать короткий латинский URL: и сканируется стабильнее, и содержимое потом можно поменять.
  • Русские домены — риск. Адреса вида «сайт.рф» безопаснее записывать в punycode (xn--), иначе часть сканеров не распознает ссылку как ссылку.
  • Проверяйте на разных устройствах. Собрали код с русским текстом — просканируйте на Android и на iPhone, а для склада ещё и терминалом сбора данных.

Проверить это можно за минуту: соберите код с русским текстом в генераторе QR-кодов и просканируйте камерой. Пошаговый разбор режимов, размеров и уровней коррекции — в инструкции о том, как создать QR-код.

С линейными штрих-кодами всё жёстче: Code 128 кодирует только набор ASCII, кириллицы там не бывает в принципе. Попытка зашить русский артикул закончится либо ошибкой генератора, либо мусором. Для внутренних этикеток русское наименование печатают отдельной строкой рядом с кодом, а в сам код кладут латинско-цифровой идентификатор.

Чек-лист: как не плодить кракозябры

  • Единый стандарт на всё: UTF-8 без BOM для кода, шаблонов, API, конфигов и логов.
  • Исключение ровно одно: CSV, который откроют в Excel на Windows, — там BOM нужен.
  • В MySQL — utf8mb4 для таблиц, колонок и соединения, чтобы не терять эмодзи.
  • Кодировка объявляется явно везде, где возможно: HTTP-заголовок, meta-тег, MIME-заголовки письма, параметр charset в строке подключения.
  • Файлы от контрагента конвертируются в UTF-8 на входе, до попадания данных в систему.
  • Ломаный текст никогда не сохраняем поверх исходника: сначала копия, потом эксперименты.
  • Имена файлов для обмена и текст внутри QR-кода — по возможности латиницей.

Частые вопросы

Почему вместо русских букв отображаются кракозябры?

Потому что программа читает байты не в той кодировке, в которой они были записаны. Текстовый файл не хранит внутри название своей кодировки — там только числа от 0 до 255, а таблица соответствия «число — символ» подставляется снаружи. Если файл записан в UTF-8, где русская буква занимает два байта, а редактор читает его как однобайтовую Windows-1251, каждая буква распадается на пару символов: получается характерный вид «РџСЂРёРІРµС‚» с обилием заглавных «Р» и «С». Если наоборот, текст в Windows-1251 читают как UTF-8, декодер натыкается на невалидные последовательности и подставляет ромбики с вопросительным знаком. Третий типовой случай — текст в 1251 прочитали в западноевропейской Latin-1, тогда «Привет» превращается в «Ïðèâåò». В первых двух случаях сам текст цел и чинится сменой кодировки при открытии. Необратимой поломка становится тогда, когда сломанный текст сохранили поверх исходника: символы замены уже заменили собой реальные байты.

Как исправить кодировку в CSV или Excel?

Excel на русской Windows при двойном клике по файлу .csv читает его в системной кодировке ANSI, то есть в Windows-1251, и файл в UTF-8 без метки BOM превращается в кракозябры. Есть три рабочих решения. Первое и лучшее, если вы управляете выгрузкой: добавить в начало файла три байта BOM (EF BB BF) — в Python это кодировка utf-8-sig при записи, в других языках достаточно вывести их перед данными. Excel распознаёт метку и открывает файл корректно. Второе: не открывать файл двойным кликом, а импортировать через вкладку «Данные» — «Получить данные» — «Из текста или CSV», выбрав в мастере источник «65001: Юникод (UTF-8)». Третье: пересохранить файл в Windows-1251, если получатель — старая программа. Отдельная беда рядом — разделитель: Excel в русской локали ждёт точку с запятой, а стандартный CSV использует запятую, поэтому строка сваливается в одну ячейку. Помогает служебная первая строка sep=, или выгрузка сразу с точкой с запятой. Когда получатель — человек, надёжнее отдавать XLSX: там кодировка зафиксирована.

Чем UTF-8 отличается от Windows-1251?

Windows-1251 — однобайтовая кодировка: каждый символ занимает ровно один байт, всего 256 позиций, первые 128 совпадают с ASCII, остальные отданы кириллице и служебным знакам. Русский текст в ней компактен, но других алфавитов в файле быть не может, и эмодзи она не поддерживает в принципе. UTF-8 — кодировка переменной длины: латиница и цифры занимают один байт и совпадают с ASCII, кириллица — два байта, редкие символы и эмодзи — три или четыре. За счёт этого UTF-8 покрывает весь реестр Unicode и позволяет держать в одном файле русский, китайский и математические знаки одновременно. Второе важное отличие — проверяемость: структура UTF-8 строгая, продолжающие байты обязаны иметь определённый вид, поэтому по самому файлу можно с высокой уверенностью сказать, UTF-8 это или нет. У Windows-1251 любой байт валиден, и определить её можно только статистически, по частоте букв. Практический вывод: для всего нового берите UTF-8, а 1251 оставьте для обмена со старыми учётными системами и оборудованием.

Как определить кодировку файла?

Надёжного стопроцентного способа нет, но есть иерархия признаков. Сначала проверьте начало файла на BOM: последовательность EF BB BF означает UTF-8, FF FE — UTF-16 LE, FE FF — UTF-16 BE. Если метки нет, попробуйте прочитать файл как UTF-8: структура у этой кодировки строгая, и случайный однобайтовый текст ей почти никогда не соответствует, поэтому успешное чтение файла с байтами выше 127 — сильный аргумент за UTF-8, а ошибка декодирования однозначно исключает его. Дальше остаётся различить однобайтовые кодировки — Windows-1251, CP866, KOI8-R, — а это возможно только статистически: библиотеки chardet и charset-normalizer декодируют текст всеми вариантами и выбирают тот, что даёт наиболее правдоподобный русский. На коротких строках такое определение часто ошибается. Самый быстрый ручной способ — посмотреть на файл в шестнадцатеричном виде: русские буквы парами с первым байтом D0 или D1 означают UTF-8, одиночные байты в диапазоне C0–FF — скорее всего Windows-1251. В повседневной работе достаточно строки состояния Notepad++ или VS Code.

Почему кракозябры появляются в письмах и SMS?

В электронной почте кодировка объявляется отдельно для тела письма и для заголовков. Тело сопровождается строкой Content-Type с указанием charset и способом переноса — base64 или quoted-printable, и если отправитель указал одну кодировку, а отправил другую, получатель увидит кракозябры. Тема письма ломается чаще тела, потому что заголовки почтового протокола исторически допускают только ASCII: нелатинскую тему полагается упаковывать в конструкцию вида =?UTF-8?B?...?= по правилам RFC 2047. Самописные скрипты рассылки часто этого не делают и отправляют тему сырым текстом — тогда письмо читается нормально, а тема превращается в мусор. С SMS механика другая: семибитный алфавит GSM-7 кириллицу не содержит, поэтому при появлении хотя бы одной русской буквы оператор переключается на двухбайтовое представление UCS-2. Кракозябры возникают, когда шлюз или CRM отправляет текст в неверной кодировке либо обрезает сообщение по границе многобайтового символа. Побочный эффект переключения — ёмкость сообщения падает со 160 символов до 70, из-за чего рекламные рассылки нередко пишут транслитом.

Что делать, если вместо текста одни вопросительные знаки или ромбики?

Это худший из вариантов, потому что он обычно означает потерю данных. Ромбик с вопросительным знаком — это символ замены Unicode: декодер встретил байтовую последовательность, которая не является валидным UTF-8, и подставил заглушку вместо непонятного байта. Обычный вопросительный знак появляется в другом случае: текст сохраняли в кодировку, где нужного символа не существует, например кириллицу в ASCII или Latin-1, и каждая русская буква превратилась в «?». Ключевой момент: если такой текст только отображается на экране, а в файле лежат исходные байты, всё поправимо — достаточно переоткрыть файл в правильной кодировке. Но если файл в таком виде сохранили, исходные байты уже перезаписаны заглушками, и восстановить их нечем. Порядок действий такой: не сохраняйте файл, сделайте копию, откройте её с опцией «интерпретировать как» и переберите Windows-1251, CP866, KOI8-R. Если ни один вариант не даёт читаемого текста — ищите исходник: резервную копию, оригинальную выгрузку или письмо, из которого файл пришёл.