Невидимые символы в локализации 2392 Android-приложений: что показала проверка каталогов F-Droid
Автоматический аудит строковых ресурсов тысяч открытых Android-приложений нашёл в переводах десятки тысяч невидимых символов — нулевые пробелы, мягкие переносы, управляющие знаки биди. Каталоги, собранные в Weblate, содержат заметно меньше мусорных U+200B, но не чище по другим классам символов.
Невидимые символы в переводах — не гипотетическая проблема: они реально доходят до сборок приложений, и часть из них убирается инструментами локализации, а часть — нет.
Что произошло
Команда Munim, Inc. (проект MV2) провела массовый анализ строковых каталогов Android-приложений из индекса F-Droid. Проверялась одна конкретная вещь: встречаются ли в переводах «невидимые» символы — нулевой пробел (U+200B), мягкий перенос (U+00AD), маркер порядка байтов (BOM), управляющие знаки двунаправленного текста (bidi) и похожие знаки форматирования, которых нет в исходной английской строке.
Такие символы на экране не видны. При этом они ломают поиск по строкам, сравнение строк при проверках и тестах, иногда — переносы и выравнивание текста. Попадают они в переводы обычно через копирование из веб-страниц, CAT-инструментов и чужих файлов локализации.
Масштаб выборки впечатляет: 4693 репозитория из индекса F-Droid, из них 2392 содержат переведённые строки, суммарно около 14,4 млн переведённых единиц. Репозитории разделили на две группы: 493 проекта, где история коммитов показывает использование Weblate, и 1899 остальных.
Зачем это важно
Проблема невидимых символов — из разряда тех, что всплывают только на этапе отладки. Пользователь видит «сломанный» поиск: строка вроде бы есть в приложении, но по запросу не находится. Разработчик тратит время на выяснение причины и обнаруживает в конце строки символ, которого ни один редактор не показывает. Инструменты сборки и автоматические тесты на сравнение строк тоже дают ложные срабатывания.
Weblate — один из самых распространённых инструментов совместной локализации в open source — такую проблему частично решает. Его автоправка RemoveZeroSpace удаляет нулевой пробел U+200B, если в исходной строке его нет. Для кхмерского языка правило не применяется — там нулевой пробел является нормальной частью орфографии.
Вопрос, на который отвечала проверка, звучит просто: заметно ли действие этой автоправки в реальных сборках приложений, а не только в самом Weblate?
Методика и её слабые места
Подход был предварительно зарегистрирован: до чтения данных сформулированы четыре прогноза, которые затем проверялись на корпусе. Это хороший тон для такого рода исследований — он защищает от подгонки выводов под уже увиденный результат.
При этом важно понимать ограничения. Платформа определялась по истории коммитов, то есть классификация «Weblate / не Weblate» косвенная и вполне может содержать ошибки. Кроме того, работа выполнена автоматическими AI-агентами: отдельные находки не проверялись человеком. Munim, Inc. прямо это указывает. Это не отменяет статистической картины, но требует осторожности с единичными примерами и точными цифрами.
Отдельно стоит отметить, что в публикации нет утверждений о том, что символы вызвали реальные сбои у пользователей — показана только их наличие в каталогах.
Результаты
Сравнение шло по двум группам. Первая — собственно нулевые пробелы U+200B, которые Weblate умеет удалять. Вторая — «контрольный класс»: мягкие переносы, bidi-управляющие символы, BOM, word joiner и случайные знаки форматирования, которые Weblate не трогает.
| Показатель | Weblate | Без Weblate | Отношение |
|---|---|---|---|
| Лишних U+200B на млн единиц | 17 | 70 | 0,25 |
| Лишних символов контрольного класса на млн единиц | 531 | 303 | 1,75 |
Репозитории на Weblate отдают примерно вчетверо меньше случайных нулевых пробелов — соотношение 0,25. Это согласуется с тем, что автоправка работает и её эффект виден в итоговых файлах. Цифра устойчива: если выбросить самый «грязный» репозиторий с любой стороны, отношение остаётся в пределах 0,21–0,27.
Вторая строка таблицы не менее показательна. По символам, которые Weblate не исправляет, его каталоги, наоборот, содержат больше мусора — отношение 1,75. То есть Weblate-проекты не «чище в целом»: они чище ровно по тому классу, который инструмент обрабатывает автоматически. Это и есть главное подтверждение того, что разница обусловлена именно автоправкой, а не каким-то общим качеством переводов.
При этом исследование носит наблюдательный характер. Оно фиксирует разницу между группами, но не доказывает причинно-следственную связь в строгом смысле — на результат могли повлиять и другие факторы: состав проектов, дисциплина переводчиков, требования к качеству в конкретных сообществах.
Два проваленных прогноза
Из четырёх предварительных прогнозов два не подтвердились, и причина показательна для методологии подобных аудитов.
Все 3665 случаев «маркеров слева направо в английских строках» оказались из одного места: псевдолокали values-en-rXC, сгенерированной машиной для тестирования интерфейса. Она встречается всего в 8 репозиториях. Каждая строка там представляет собой длинную последовательность знаков U+200E/U+200F, за которой следует английский текст. Авторы исключили из анализа известные тестовые псевдолокали en-XA и ar-XB, но про en-XC не знали.
В итоговой записи эти прогнозы оставлены помеченными как неудачные, а рядом приведены постфактум рассчитанные числа. Такой подход к публикации результатов — с сохранением неудачных гипотез — выглядит корректнее, чем тихое переписывание выводов.
Как выглядят находки
Характер находок важнее сводных цифр, потому что показывает, где искать проблему в своих проектах:
-
Мягкие переносы в основном намеренные — это точки переноса внутри длинных составных слов в нидерландском, немецком и норвежском (
Standaard<U+00AD>videoformaat). Примерно 18% выглядят случайными: в начале строки, после последней буквы или после китайского слова. -
Нулевые пробелы — преимущественно мусор от копирования, застрявший в середине предложения. В одном приложении удвоенная пара таких символов стоит в одном и том же месте сразу в нескольких языках — типичный признак единожды испорченной строки, размноженной по переводам.
-
Встречаются случайные маркеры порядка байтов по краям строк и невидимый разделитель U+2063 в конце латышских строк.
Что с этим делать
По итогам проверки в собственный инструмент авторов — open-source проверщик переводов l10n-guard под лицензией Apache-2.0 — добавлено предупреждение invisible-char в версии 0.15.0. Инструмент также работает как GitHub Action и сообщает о невидимых символах, которых нет в исходной строке.
Правила подавления срабатываний настроены с учётом языковой специфики: не репортуются нулевые пробелы в тайском, лаосском, тибетском, бирманском и кхмерском языках, управляющие знаки и bidi-символы в текстах с письмом справа налево, нулевые соединители/разъединители, а также мягкие переносы между двумя буквами. Такие исключения делают предупреждение пригодным для реальной CI-проверки без потока ложных срабатываний.
Для команд, которые не хотят подключать новый инструмент, выводы исследования применимы и вручную: стоит прогнать проверку строковых ресурсов на U+200B, U+00AD, BOM и bidi-управляющие символы в переводах, отдельно посмотрев на строки, пришедшие из внешних CAT-систем.
Материалы исследования — предварительная регистрация, код, результаты и данные под лицензией CC BY 4.0 — опубликованы в репозитории android-translation-crash-census на GitHub, там же лежит проект l10n-guard.
Итог
Проверка показывает две разные вещи одновременно. С одной стороны, автоматическая зачистка невидимых символов на уровне платформы локализации действительно работает: её эффект виден в тысячах финальных файлов, а не только в интерфейсе инструмента. С другой, никакая автоправка не заменяет системную проверку — Weblate-проекты не стали чище по мягким переносам, BOM и bidi-символам, а по этим классам мусора у них даже больше.
Практический вывод простой: невидимые символы стоит ловить автоматически на этапе сборки, с белым списком исключений для языков, где такой символ нормативен. Ручной аудит здесь малоэффективен — именно потому, что проблема на экране не видна.