Каждому языку - свой адрес
Главная ошибка - переключать язык скриптом на одной и той же странице. Поисковик индексирует адреса, а не состояние кнопки: он увидит одну версию, обычно ту, что показывается по умолчанию, и казахская или английская версия в поиск не попадёт.
Правильно - отдельный адрес для каждого языка: /kk, /ru, /en или поддомены. Тогда каждая версия индексируется отдельно, у каждой свой title и description, и её находят по запросу на своём языке.
hreflang: чтобы поиск показывал нужную версию
Языковые версии одной страницы связывают разметкой hreflang. Каждая версия перечисляет все варианты, включая саму себя, и вариант по умолчанию x-default. Коды языков: kk для казахского, ru для русского, en для английского.
Разметка должна быть взаимной: если казахская страница указывает на русскую, русская обязана указывать на казахскую, иначе поисковик связь проигнорирует. Мы держим список языковых адресов в одной функции и берём его и для страниц, и для sitemap.xml - так две разметки не расходятся.
Переводить нужно не только текст
- title и meta description каждой страницы
- превью для соцсетей: заголовок, описание и картинка с текстом на языке страницы
- alt у изображений
- разметка schema.org: названия и описания
- атрибут lang у тега html
- кнопки, ошибки, подсказки и письма
- форматы дат, чисел и валюты
Если хоть что-то из этого осталось на русском, казахская версия выглядит недоделанной - и для людей, и для поисковика.
Шрифт должен знать казахские буквы
В казахском алфавите девять букв, которых нет в русском: ә, ғ, қ, ң, ө, ұ, ү, һ, і. Не каждый шрифт их содержит, а если содержит, они часто лежат в отдельном наборе символов cyrillic-ext, который нужно подключить явно.
Когда буквы нет, браузер подставляет её из системного шрифта, и в слове появляется символ другой формы. Мы сами на этом попадались: у моноширинного JetBrains Mono в сборке Google Fonts нет заглавных Ә, Ғ, Қ, Ң, Ұ, Һ, и на этом сайте его пришлось заменить на IBM Plex Mono. Похожая история со знаком тенге: в шрифтах Google Fonts ₸ лежит в расширенном латинском наборе latin-ext, и без него сумма выводится пустым квадратом.
Проверка занимает минуту: наберите на странице «ӘҒҚҢӨҰҮҺІ әғқңөұүһі ₸» каждым шрифтом, который используется на сайте, и посмотрите, не выбивается ли какой-то символ.
Формы слов при числах
В русском у слова после числа три формы: 1 проект, 2 проекта, 5 проектов. В английском две: 1 project, 5 projects. В казахском существительное после числа не меняется: 1 жоба, 5 жоба.
Поэтому склеивать «число + слово» по правилам одного языка нельзя. В браузере и Node.js для этого есть стандартный Intl.PluralRules: он возвращает нужную категорию формы для каждого языка, а сами формы лежат в словаре.
Числа одинаково на сервере и в браузере
Разделитель тысяч в русском и казахском - пробел, в английском - запятая. Если сайт форматирует числа и на сервере, и в браузере, стандартное форматирование может выдать разные пробелы: обычный в одном месте и узкий неразрывный в другом. React видит несовпадение разметки и перерисовывает блок. Мы в итоге задаём разделители явно для каждого языка.
Как переключать язык
- Переключатель ведёт на ту же страницу на другом языке, а не на главную
- Язык по настройкам браузера определяем только при входе на корень сайта; на внутренних адресах язык задаёт сам адрес
- Не перенаправляем по IP-адресу: поисковые роботы приходят из других стран и не увидели бы казахскую версию
- Названия языков в переключателе пишем на самих языках: Қаз, Рус, Eng
Как не потерять перевод
Когда тексты лежат в словарях по языкам, легко добавить строку в русский и забыть казахский. Мы проверяем это автоматически: тест сравнивает структуру всех словарей и падает, если в каком-то языке не хватает ключа или строка пустая. Так новая страница не уходит в прод наполовину переведённой.
Чек-лист
- Отдельный адрес для каждого языка
- Взаимный hreflang и x-default на каждой странице и в sitemap.xml
- Переведены title, description, превью для соцсетей, alt и разметка
- Шрифты показывают ә, ғ, қ, ң, ө, ұ, ү, һ, і и ₸
- Формы слов при числах - по правилам каждого языка
- Переключатель ведёт на ту же страницу
- Нет редиректа по IP
- Автоматическая проверка, что ни один язык не отстал

