[Из песочницы] Альтернативный способ локализации веб-сайтов: мутирующий контент CDN

Вступление


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

В этой статье я хотел бы вынести на обсуждение альтернативную модель перевода веб-сайтов. Если попытаться описать принцип в одном предложении, то это: CDN, который переводит контент между пользователем и оригинальным источником.

Необходимость в переводах


Сомневаюсь, что пользу от многоязычности ресурса стоит доказывать, но, тем не менее, посвящаю этому один маленький параграф.

Любой Интернет-сайт доступен трем миллиардам пользователей на планете по умолчанию — просто потому что ваш сайт в Интернете. Если вы что-то продаете на сайте, то простым добавлением языка вы фактически выходите на новый рынок. Как минимум, вы захотите иметь версию на местном языке (языке территории, где вы ведете бизнес) и английскую версию, ведь английский язык — это половина Интернет-контента по данным W3Techs.

Существующие способы


Файлы с переводами


Варианты имеются разные — от специальных форматов вроде GNU gettext, до простых текстовых файлов, которые ваш текущий framework может использовать. Результат примерно одинаковый: в момент вывода текста вызывается функция, которая проверит наличие перевода в словаре.

Пример на PHP:

// gettext:
echo _("Привет, мир!");

// Фреймворк Laravel 5
echo trans('common.hello_world');


Плюсы способа:

  • Проверенный десятилетиями метод (читай привычный);
  • Возможность достаточно просто отдавать отдельные файлы сторонним переводчикам;
  • Маленькое влияние на код.


Минусы способа:

  • Как правило отсутствует возможность внесения немедленных изменений;
  • gettext-словари нужно компилировать, а конечные файлы коммиттить в репозиторий;
  • Относительно неудобное и медленное управление текстами, чем больше проект — тем больше файлов и запутаннее иерархия;
  • Нет стандартных механизмов для работы над переводами командой переводчиков;
  • Как правило, используется две схемы параллельно — переводы для backend и переводы для frontend (JavaScript);
  • Время от времени в текстах для переводов появляются HTML-тэги, потому что программистам было неудобно их выдергивать.


Переводы в базе данных


В отличии от первого способа, переводы хранятся в базе данных проекта, что позволяет вносить корректировки на ходу. Как правило, разработчики проекта делают для администраторов панель управления переводами, на что уходит дополнительное время.

Плюсы способа:

  • Легче организовать работу команд;
  • Возможность внесения немедленных изменений.


Минусы способа:

  • Сложнее отдавать разделы на перевод сторонним переводчикам;
  • Frontend тексты переводятся всё ещё отдельно от backend текстов;
  • Время от времени в текстах для переводов появляются HTML-тэги, потому что программистам было неудобно их выдергивать.


Перевод на стороне пользователя через JavaScript


Сравнительно новый способ, предлагается сейчас несколькими западными стартапами. От вас требуется только добавить ссылку на внешний JavaScript-файл, который начнет подменять тексты в DOM на основе предоставленных (или утвержденных) заранее переводов.

Плюсы способа:

  • Простая установка практически без необходимости программирования;
  • Frontend и backend переводятся одновременно из одного репозитория переводов;
  • В репозитории текстов не будет HTML-тэгов, потому что все тексты обрабатывались post factum из DOM.


Минусы способа:

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


CDN-переводчик


Собственно, то, что выносится на обсуждение в данной статье. А что если между пользователем и сайтам вставить «прослойку» — пограничный сервер, способный переводить web-контент? Сервисы вроде CloudFlare уже умеют минимально мутировать клиентские страницы — добавлять код Google Analytics, к примеру. Что если сделать шаг дальше и позволить пользователю подменять тексты и ссылки?

Поведение традиционного CDN:

  1. Клиент запрашивает адрес X;
  2. Если адрес X есть в кэше, то он немедленно возвращается из кэша;
  3. Если адреса X нет в кэше, то пограничный сервер делает запрос на оригинальный сайт, а затем возвращает ответ клиенту. В зависимости от заголовков в ответе оригинального сайта и правил, установленных на сайте, ресурс X теперь может быть помещен в кэш.


1576c0cacd7a4dc99dfb83d31802f0aa.jpg

Поведение CDN-переводчика:

  1. Клиент запрашивает адрес X;
  2. Если адрес X есть в кэше, то он немедленно возвращается из кэша как есть;
  3. Если адреса X нет в кэше, то пограничный сервер делает запрос на оригинальный сайт, а затем применяет правила мутации — подменяет ссылки, заменяет переведенные тексты. В зависимости от заголовков в ответе оригинального сайта и правил, установленных на сайте, ресурс X может быть помещен в кэш.


Шаг 2b в деталях


Получив ответ от оригинального сайта, у пограничного сервера стоит задача, как его перевести. Предлагаемая тактика:

  1. Обратить внимание на заголовок Content-type. Если значение не входит в список поддерживаемых, то не пытаться трансформировать контент;
  2. Обратить внимание на размер ответа. Если размер выше установленной границы — не пытаться трансформировать контент;
  3. Начать парсинг и редактирование контента. Пример для HTML-страницы: пройтись по всем узлам DOM, у которых есть текстовый узел-потомок. Запросить в репозитории переведённый текст, передав как параметры исходный текст и контекст.
  4. Заменив необходимые куски контента, возвращаем результат пользователю. Если заголовки и правила позволяют, то кэшируем результат.


Репозиторий было бы логично реализовать как отдельностоящий RESTful API, а контекст было бы удобно задавать вроде URL: selector. К примеру, хотим переводить слово «Main page» как «Главная» в любом блоке любой страницы начинающейся на /news, получаем контекст »/news*: head». Мир настолько привык к селекторам в стиле CSS/jQuery, что начать работать с таким синтаксисом сможет практически любой разработчик с ходу.

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

Предположим, что у нас приложение на PHP и используется фреймворк Laravel. Реализовать legacy-поддержку тривиально — пере-объявляем функцию-помощник trans (), заменяем её своей реализацией, где поиск идёт не в локальных текстовых файлах, а в удалённом API. Чтобы избежать задержек при каждом запросе, используем кэш или отдельный процесс-proxy.

Подобным образом, можем менять содержимое объектов JavaScript, графические изображения и так далее.

Плюсы способа:

  • Полная абстракция приложения и переводов — приложение вообще не знает о наличии других языковых версий. Программисты спокойно работают над основным продуктом;
  • Backend и frontend-контент переводится одновременно, используя один репозиторий переводов;
  • Можно достаточно просто переводить графические изображения;
  • Очень просто запускать переведенные версии сайта на других (отдельных) доменах;
  • Совместимость с любым существующим сервисом CDN. Можно выстраивать в цепочку;
  • Совместимость с поисковыми системами и социальными сетями;
  • В репозитории текстов не будет HTML-тэгов, потому что все тексты обрабатывались post factum из DOM;
  • Легко организовать работу команд.


Минусы способа:

  • Мне не удалось найти, но буду очень рад помощи в этом!


Видео YouTube


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

Реализация


Я уже проверил реализуемость и практичность предложенного метода — написал примитивный вариант пограничного приложения на PHP и Lumen.

Мой метод, получающий от пользователя запрос и возвращающий переведенный ответ:

/**
 * @param Request $request
 * @param WebClientInterface $crawler
 * @param MutatorInterface $mutator
 * @param TranslatorInterface $translator
 * @return Response
 */
public function show(Request $request, WebClientInterface $crawler, MutatorInterface $mutator, TranslatorInterface $translator)
{
    $url = $request->client['origin'] . parse_url($request->url(), PHP_URL_PATH);

    $response = $crawler->makeRequest($request->getMethod(), $url);
    if ($response === false) abort(502);

    $mutator->initWithWebRequest($response);

    if ($response->isTranslatable()) $mutator->translateText($translator);
    if ($response->isCacheable()) $mutator->cache(60);
    $mutator->replaceLinks($request->client['origin'], $request->getSchemeAndHttpHost());

    return (new Response($mutator->getBody(), $mutator->getStatusCode()))
        ->withHeaders($mutator->getHeaders());
}


Уверен, что многие начнут сомневаться в парадигме из-за нагрузки на процессор — ведь тот же nginx потому и не хочет никак мутировать содержимое ответов, что это очень негативно отразилось бы на производительности. Вообще, переводить вот так, post factum — это, безусловно, дороже с точки зрения ресурсов.

Мои аргументы здесь следующие. Мы наблюдаем постоянное удешевление IT-ресурсов в течение последних 5–10 лет, наступила эпоха серверов за 5 долларов — для многих сайтов не так уж и страшно немного повысить нагрузку. Во-вторых, если я все-таки займусь этим проектом, то оптимизация производительности будет одним из приоритетных направлений. Наверняка, можно найти много мест для улучшений!

Заключение


Индустрия всегда движется в сторону оптимизации, повышения комфорта и экономии средств. Считаю, что предложенный способ локализации веб-приложений вполне вероятно может стать основным через 5–10 лет.

Более того, у CDN, как у структуры, могут появляться всё новые и новые применения. CloudFlare предложил миру защиту от DDoS, Imgix делает адаптивные картинки на лету.

© Habrahabr.ru