Разделение кода и текста: прототип

Этот пост является продолжением предыдущей публикации Разделение кода и текста: мысли вслух. На этот раз мы пойдем чуть-чуть дальше и представим возможный API, а также сравним рабочий процесс до и после. В качестве примера использованы язык PHP и фреймворк Laravel, но это почти не имеет значения.

Тезис

В этом посте мы пытаемся разобраться, можно ли заметно улучшить процесс работы с текстом в современных приложениях. Под текстом подразумеваются любые элементы интерфейса, коммуникаций с пользователями и так далее — то, что не является частью бизнес-логики в исходном коде. Говоря проще, это файлы с шаблонами, переменные и параметры типа string и так далее.

Текущий метод #1

Для начала рассмотрим самый примитивный способ работы с текстами. Этот способ вполне себе подходит для маленьких приложений и сайтов. Текст передается как параметр внутри контроллера:

Текст являются частью шаблона:

3d14bfe1caecfdeac1626776f3566de7.png

Плюсы данного метода:

  • Программисту не надо тратить дополнительное время

  • Очень легко вставлять в текст значения из переменных

Минусы:

  • Тексты разбросаны по репозиторию — что-то находится в классах, что-то в шаблонах. Иногда нужно время, чтобы найти конкретный текст и исправить в нем ошибку

  • Нет возможности перевода на другие языки

Текущий метод #2

Этот метод из документации Laravel, и большинство современных фреймворков, написанных на самых разных языках, имеют что-то подобное. Это примерная копия технологии 1970–1980 годов под названием i18n.

Имеем отдельную папку с набором файлов, в которых хранятся все наши тексты. В коде, вместо того, чтобы писать текст напрямую, ссылаемся на эти файлы, на какой-то идентификатор конкретного текста:

c92cdb74bf25966ebc0637c61b681ffc.pngca8182888b6e926ee41840fe300e9a69.png

Плюсы метода:

  • Теперь можно переводить тексты при необходимости, достаточно перевести файлы, в которых хранятся тексты

  • Тексты вынесены в отдельную папку, что позволяет оптимизировать рабочий процесс (допустим, сделать их отдельным пакетом или репозиторием, чтобы копирайтеры и переводчики могли там делать изменения сами)

Минусы метода:

  • Дополнительная, замедляющая работа для разработчика, причем с ростом приложения она сильно увеличивается. Часто возникает дилемма — использовать существующий текст или создать новый? А вдруг в этом новом месте чуть-чуть другой контекст?

  • Вставлять переменные в текст не так удобно, как в варианте #1

  • Опечатка в идентификаторе текста «поломает» его

  • Работая с текстами копирайтеры и переводчики могут не до конца понимать, в каком месте конкретно он используется, «влезет» ли он в интерфейс и так далее

Текущий метод #3

Этот метод является модификацией предыдущего. Вместо использования идентификаторов, в целях ускорения работы сразу подставляется текст на языке по-умолчанию (например, на языке разработчика):

02c39115fe4dbeb5abd99bbf58af3fe5.png2ac5cf74eb751dc03be5ce43ececf2c9.png

Плюсы метода:

  • Программист не тратит лишнего времени, как и в варианте #1

  • Можно комфортно переводить продукт на другие языки или локали

Минусы метода:

  • Переменные снова не так удобно вставлять внутрь текста

  • Если программист сделал опечатку или просто написал плоховато, то копирайтеру надо идти в код, чтобы исправить

  • Тексты теперь разнесены по 2 местам — переводы находятся в папке с текстовыми файлами, а тексты на основном языке находятся прямо в коде

  • Все еще не до конца понятно где находится в продукте конкретный текст. Копирайтер или переводчик может выбрать неудачный или неподходящий (по тону, по длине) текст

  • Невозможность иметь разные переводы для одного и того же исходного текста, так как сам текст является уникальным идентификатором

Предлагаемый метод

А теперь рассмотрим предлагаемый данной статьей метод, в котором мы попытаемся объединить плюсы каждого из вышеперечисленных. Предлагается использовать супер-короткую глобальную функцию (наподобие __()) в коде и директиву в файлах шаблонов (допустим, @p):

a775347cd076f0b34bf8572ab99dea34.png

Или даже вот так:

ed2974c4150b4427341e11ca63aa4364.png

Это смесь всех вышеуказанных методов. С одной стороны, мы работаем с текстом по привычному — мы можем взять и вставить переменную внутри, где ходим. С другой стороны, у нас есть специальный класс, который умеет переводить тексты.

Что же делает глобальная функция или директива p:

  • Подобно функции __() она проверяет в специальных файлах наличие альтернативного текста, которым может быть как перевод на другой язык, так и просто поправленная версия на этом же языке

  • Все отсутствующие тексты записываются в специальной лог, для дальнейшего экспорта

  • Функция использует трассировку языка и статический анализ, чтобы понять откуда был вызов — с какой страницы, какого шаблона и так далее

Таким образом, предполагается, что где-то в процессе CI/CD будет происходить синхронизация  — мы будем извещать внешний API о новых текстах, которые нуждаются в переводе или поправках, включая их точное местоположение, а также загружать в локальные файлы существующие тексты.

Это создает целый ряд плюсов. Рассмотрим их детально.

Преимущество #1

Как известно, многие программисты говорят даже на своих родных языках заметно слабее Шекспира или Пушкина. Тем не менее, с этим подходом разработчики могут не терять лишнее время, а просто писать черновые версии текста:

d873492e5d34f6a1205d7c6604eb2467.png

Вот соответствующее всплывающее сообщение на сайте:

ce86b20e3ade5edcbd1473ea10455516.png

Вроде бы и неплохо, но как-то слишком лаконично и сухо, кто-то даже может обидеться. Копирайтер (ну или какой-то product manager) вскоре увидит в своем интерфейсе, что появился новый текст на сайте и правит его на что-то более дружелюбное или соответствующее брэнду:

ace14398668a638248203daee9e5721b.png

Что после следующей синхронизации попадает в интерфейс продукта:

fbbecc5bb05a824def6d9b01e759c721.png

Программист при этом не трогал код, да и не будет трогать эту часть кода никогда. Ведь это не часть бизнес-логики. Тексты целиком и полностью управляются из удобной админки. Нет никаких коммитов в репозиторий, чтобы поправить опечатку или улучшить текст.

То есть первое преимущество — это снятие с плечей разработчиков забот о качественных текстах. Программисты делают свою работу, а копирайтеры делают свою.

Преимущество #2

Удобный общий контроль всех текстов в продукте — языка, на котором мы говорим с клиентами. Можно в любую секунду посмотреть как сейчас мы говорим на странице X или в письме Y, влазит ли тайский перевод в кнопку, достаточно ли эмпатии в письме про отмену транзакции:

6149fc2f4fed8519e9df871bae304bc1.png

Если раньше коллеги спрашивали: «а что мы сейчас говорим на такой-то странице?», а мы отвечали: «непомню, вроде вот это», то теперь любой сотрудник может посмотреть любой шаг и предложить поправки или улучшения в пару кликов. Тексты, разбросанные по сотням или тысячам файлов теперь собраны в одном месте и разложены аккуратно по полочкам, с полноценным предпросмотром:

Гипотетический предпросмотр (рендер)Гипотетический предпросмотр (рендер)

Преимущество #3

Такой фичи, во всяком случае публично, пока нигде нет, но появляется интересная возможность создавать что-то вроде диалектов — тексты, адаптированные по тону и лексикону под конкретного пользователя, точнее группу пользователей. Пользователи младше 30 лет получают сообщения в более веселом тоне, с модными словечками, а люди старше 50 — нейтральные, уважительные тексты. В теории это поднимет конверсии за счет повышения рапорта с пользователем, и логично предположить, что полезно это будет в основном продуктам с разными сегментами пользователей.

То есть, имеем локаль ru_RU — русский язык в Российской его версии, и на его основе создаем ru_RU-молодежный .

Преимущество #4

Появляется сильно упрощенная возможность проведения тестов A/B. Как многим давно известно, текст напрямую влияет на уровень конверсий на странице — исправив одно слово, можно замотивировать большую группу пользователей что-то сделать, или наоборот перестать что-то делать. Сейчас на рынке нет прямо уж очень простых способов делать такие тесты. Как правило, разработчики делают что-то вроде:

c7dcc9bfc0c3cd41c115859d75a3a1be.png

Имеем два шаблона home.blade.php и home__treatment.blade.php, в первом у нас так называемая control-версия теста, его старый вид, а во втором файле treatment-версия — версия, эффективность которой мы тестируем. Пользователям при посещении страницы выставляется кука или флаг в сессии, которая будет определять какую версию в дальнейшем они будут видеть. Кроме того, аналитика (допустим, Google Analytics) будет также получать этот флаг, чтобы в дальнейшем можно было делать сравнительный анализ.

Представим, что в нашей гипотетический админке появилась еще кнопка «A/B-тест»:

bd84269b66de57178ddd177dc230202f.png

Нажали на кнопочку и предоставили вторую версию текста:

b37ec42c7e640bb466b61f2005db4dab.png

А наш сервис аналитики (скажем, Google Analytics) берет правильную метку (control или treatment) либо из нашего приложения, либо из этого сервиса по работе с текстами. Добавили вторую версию текста и через неделю посмотрели как изменился Bounce Rate или количество кликов на странице, оставили одну — ту, что эффективнее. Тривиально.

Преимущество #5

Подключать сервисы переводов (вроде Gengo) или сервисы исправления грамматических ошибок настолько тривиально, что даже не стоит подробно об этом писать ;)

Послесловие

Конечно, тут бы имела место быть какая-то цена миграции — время и силы, затраченные на то, чтобы обернуть все тексты в коде в эти функцию и директиву, но этот момент присутствует и в существующих в индустрии вариантах #2 и #3. Хочется добавить, чтобы преимуществ от подобного сервиса достаточно много даже для моно-языковых продуктов.

Контролировать практику обертки текстов можно через простой webhook, тогда все pull-реквесты будут автоматически проверятся:

2640bc513c543377bbfc87550085f1c7.png

Несмотря на то, что я немного поигрался с кодом и проверил, что все вышеизложенное возможно, эта статья обсуждает гипотетический продукт или утилиту. Цель статьи — собрать мнения, ваши мнения.

Пользовались бы Вы такой вещью?

© Habrahabr.ru