Локализация встраиваемого виджета: язык страницы или язык браузера?

обновления продукта

Посетительница испанского интернет-магазина находит кнопку оформления заказа, которая ничего не делает. Она нажимает на маленький значок жука в углу и видит «What's on your mind?» и кнопку «Send». Разобраться она может. Но пишет три слова вместо трёх предложений, потому что описывать проблему на чужом языке — это усилие, и никто не обязан тратить его ради вас.

До этой недели так было на каждом неанглоязычном сайте с BugDump. Виджет говорил по-английски, а чтобы перевести его, нужно было самому передать все 36 строк. Большинство этого не делали. Теперь он поставляется на английском, русском, испанском, французском и португальском и сам выбирает язык. Самым интересным оказался как раз выбор.

Обновление от 3 октября: шестым языком в SDK 0.0.42 стал немецкий. Ему тоже понадобились слова покороче: на кнопке прикрепления написано «Datei» (файл), а на значке голосовой заметки — «Audio». Подробности — в записи журнала изменений.

Виджет баг-репортов BugDump на французском, испанском, португальском и русском

Три источника, из которых виджет узнаёт язык

Встраиваемый виджет работает на чужой странице. Он может прочитать три сигнала, и они не всегда совпадают.

Атрибут lang страницы. <html lang="es"> — так сайт сообщает, на каком языке его контент. Скринридеры выбирают по нему произношение, а браузеры смотрят на него, когда предлагают перевести страницу. Он описывает страницу, а не того, кто её читает.

Список языков браузера. navigator.languages — предпочтения посетителя по порядку, что-то вроде ["pt-BR", "pt", "en"]. navigator.language — только первый элемент. Он описывает человека, а не страницу.

Что сайт говорит виджету. Явная настройка в коде встраивания. Самый надёжный из трёх сигналов — и тот, который почти никто не заполняет.

Есть ещё заголовок Accept-Language, который сервер видит в каждом запросе. Виджет, загруженный как скрипт с CDN, прочитать его не может, так что этот вариант отпадает.

Почему я начинаю со страницы

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

Поэтому я выбрал такой порядок:

  1. <html lang>, если там указан язык, на котором поставляется виджет.

  2. Иначе — первый из языков браузера посетителя, на котором он поставляется.

  3. Иначе — английский.

Страница идёт первой, чтобы английский сайт оставался английским, даже если у посетителя браузер на русском. Браузер идёт вторым, чтобы страница вообще без атрибута lang всё равно получила что-то разумное.

Очевидная дыра — страница с неправильным lang. Многие стартовые шаблоны поставляются с <html lang="en">, и его никто никогда не трогает, так что сайт целиком на португальском может по-прежнему заявлять, что он английский. Я решил, что исправлять это нужно в атрибуте, а не более хитрой догадкой. Неправильный lang уже вредит пользователям скринридеров на этом сайте. Виджет, который молча это обходит, прячет проблему вместо того, чтобы её показать.

Сопоставление тегов без хитростей

Языковые теги бывают разной формы: fr, fr-CA, FR или pt_BR от бэкенда, который никогда не слышал о BCP 47. У виджета один набор строк на язык, поэтому сопоставление смотрит только на часть до первого - или _, приведённую к нижнему регистру:

JavaScript
function findLocale(tag) {
  const base = tag.toLowerCase().split(/[-_]/)[0];
  return SHIPPED_LOCALES.find((locale) => locale === base);
}

Значит, посетитель из Португалии получит бразильский португальский. Это не идеально, и я лучше скажу об этом прямо, чем буду делать вид, что всё в порядке.

Поиск по списку — намеренный. base in LOCALES читается почти так же — ровно до тех пор, пока не придёт тег вроде constructor и не совпадёт со свойством, которое наследует любой объект JavaScript. Никто не ставит lang="constructor" специально, но с точки зрения виджета атрибут lang — это ввод от незнакомца.

Что сломалось по дороге

Тест, результат которого зависел от того, чей ноутбук его запускал. В последних версиях Node есть глобальный navigator, и его languages следует локали машины. Тесты конфигурации запускаются без DOM, так что первый вариант превращал бы «auto» в русский на машине с русской локалью и в английский в CI. Теперь определение языка ничего не возвращает, если нет ещё и document, — так и должно выглядеть небраузерное окружение.

Русский — длинный. В «Прикрепить» десять букв против шести в английском Attach, и третья кнопка на панели действий из-за него переехала на вторую строку. Помог не CSS, а слово покороче: на русской кнопке теперь написано «Файл», а остальное объясняет значок скрепки. У значка голосовой заметки на вложениях была та же проблема. Это плитка шириной 72 пикселя, и «Nota de voz» там обрезается многоточием, поэтому в испанском и французском теперь «Audio», а в португальском — «Áudio».

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

Строки, склеенные в коде. «Uploading 1/3… 40%» собиралась со словом, зашитым в код, как и «Microphone 2» и «Powered by Bugdump». Вместо того чтобы изобретать синтаксис шаблонов, я сделал переводимой частью только само слово, а числа и название код добавляет после него. Это работает, потому что во всех языках, на которых поставляется виджет, слово стоит первым. В японском было бы иначе, и в день, когда я его добавлю, эти три строки станут шаблонами.

Скринридеры. Виджет рендерится в shadow root на странице сайта, поэтому наследует её lang. Французскую панель на английской странице скринридер прочитал бы с английским произношением. Теперь виджет ставит lang на свой собственный элемент.

Последнее слово за сайтом

Автоопределение — это значение по умолчанию, а не правило. Сайт может указать язык в коде встраивания:

HTML
<script
  src="https://bugdump.com/sdk/latest.js"
  async
  data-api-key="your-api-key"
  data-locale="fr"
></script>

translations по-прежнему заменяет любую строку поверх выбранного языка, так что переименовать «Send feedback» в «Report a bug» можно одинаково и во французском, и в английском. Для языка, которого нет в поставке, тоже работает locale: 'nl' плюс ваши собственные нидерландские строки, а всё, что вы не указали, откатывается на английский.

На странице «Виджет» в BugDump тот же выбор сделан выпадающим списком, и превью переключает язык сразу, пока вы выбираете:

Настройка «Язык» на странице «Виджет» в BugDump, открыт список: «Автовыбор», English, Русский, Español, Français, Português и Deutsch

Чего это стоило

Все пять языков встроены в скрипт. Четыре дополнительных добавили около 2,5 КБ в gzip, и теперь виджет весит около 32 КБ. Я думал загружать их по требованию, но это ещё один запрос и панель, которая может отрисоваться только после его ответа. Состояние загрузки ради экономии двух с половиной килобайт — плохая сделка.

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

Если вы делаете свой виджет

  • Читайте <html lang> раньше navigator.languages. Ваш виджет — часть страницы.

  • Сопоставляйте по базовому языку и объявляйте язык, который в итоге выбрали, собственным атрибутом lang.

  • Дайте сайту переопределить всё, и пусть переопределение будет одним атрибутом в теге script.

  • Перед релизом отрисуйте каждую строку на каждом языке. Переводы длиннее, чем вы ожидаете.

Виджет BugDump делает всё это начиная с SDK 0.0.41, и у тех, кто подключает его тегом script, это уже есть. Все настройки описаны в статье Перевод и ребрендинг виджета, а краткая версия — в записи журнала изменений.

Читайте также