Устранение неполадок
Каждая запись здесь описывает сбой, с которым кто-то действительно столкнулся. В каждой указаны симптом, причина и способ исправления. Большинство случаев сводится к одному из трёх: фразы нет в проекте, SDK так и не был инициализирован или плейсхолдер записан для неверного контекста.
Ничего не переводится — все фразы остаются на исходном языке
Если везде отображается исходный текст, то, скорее всего, SDK не удалось инициализировать. Он намеренно дает сбой «мягко» (ваш интерфейс не должен ломаться), поэтому единственный явный сигнал — консоль браузера, так что проверьте её в первую очередь. Обычные причины в порядке убывания вероятности:
- CORS. Первый запрос SDK —
authorize-project; если ваш API не включает origin приложения в список разрешённых, браузер блокирует запрос, и инициализация прерывается прямо на этом шаге. Помните, что origin — это точное сочетание схема + хост + порт:localhost:5173иlocalhost:5174— разные origin, а dev-серверы меняют порты, если привычный порт занят. Добавьте в список разрешённых каждый порт, на котором вы действительно ведёте разработку. - Неверные учётные данные. Отсутствующий или неправильно введённый идентификатор проекта / ключ API приводит к сбою того же вызова авторизации. В консоли отображаются неудавшийся запрос и URL.
- Вы смотрите на базовую локаль. В базовой локали «перевод» — это исходный текст, поэтому ничего заметно не меняется. Чтобы проверить, переключитесь на целевую локаль.
# The SDK's first call is an authorize-project request.
# If the browser blocks it, NOTHING translates. Check the console for:
Access to fetch at 'https://your-api/api/authorize-project/...'
from origin 'http://localhost:5173' has been blocked by CORS policy
# Fix: allowlist the app's EXACT origin (scheme + host + port) on the API.
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://app.example.com Одна фраза не переводится (а другие переводятся)
Фраза в вашем коде является ключом поиска — фраза и есть ключ. Если одна фраза отображается как исходный текст, а соседние переводятся, значит, этого ключа нет в каталоге проекта:
- Она так и не была зарегистрирована. Регистрация происходит через обнаружение токенов, и выполнить её может только ключ с правом записи. Если фраза появилась, пока приложение работало с ключом только для чтения, она никуда не отправлялась — один раз откройте этот экран в режиме разработки с ключом записи или добавьте фразу в панели управления.
- Текст изменился. Любая правка — слово, знак препинания, даже пробел — превращает его в новую фразу, у которой пока нет переводов. Старые переводы по-прежнему существуют под прежним ключом. Особенно часто это проявляется в блоках контента: блок
<Translate>разбивается на токены по своей разметке, поэтому изменение текста или структуры блока меняет то, что ищется, и блок незаметно возвращается к исходному тексту, пока новая форма не будет зарегистрирована и переведена. - Машинный перевод ещё не подтянулся. Только что зарегистрированная фраза может на мгновение опережать свои переводы, а для локали, которой нет в списке целевых языков проекта, перевода не будет вовсе. Проверьте фразу в дашборде: зарегистрирована ли она? переведена ли на эту локаль?
Почему отсутствующие фразы не вызывают ошибку?
Так задумано. В продакшене система перевода должна в худшем случае показывать читаемый исходный текст, а не ломать страницу, поэтому незарегистрированная фраза отображается в исходном виде, а SDK молчит. Обратная сторона: при ключе только для чтения отсутствующая фраза остаётся незаметной. Ничего не логируется, ничего не регистрируется, и о проблеме вы узнаете, лишь когда пользователь увидит английский текст. Предотвращает это разделение ключей: разрабатывайте с ключом на запись, чтобы обнаруживалась каждая фраза, которую отображает ваш интерфейс, а затем выпускайте с ключом только для чтения.
# Development — write key: new phrases are discovered,
# registered, and machine-translated as you build.
VITE_LANGSYS_API_KEY=write-key
# Production — read-only key: fetches the finished catalog only.
# Unregistered phrases silently render their source text.
VITE_LANGSYS_API_KEY=read-only-key Мой плейсхолдер отображается как есть — «Hi %name%» или «Hi {name}»
- В вызовах
t()плейсхолдеры записываются в одинарных фигурных скобках{name}, а интерполяция выполняется, только если передать параметры:t('Hello, {name}!', 'Greetings', { name }). - В разметке
<Translate>/<Phrase>под React, Vue или Svelte записывайте плейсхолдеры как%name%. Компилятор фреймворка воспринимает «голый»{name}как выражение ещё до того, как его увидит SDK;%name%переживает компиляцию, и SDK при захвате преобразует его обратно в{name}. Обычный{name}подходит для чистого HTML, где нет синтаксиса шаблонов, с которым мог бы возникнуть конфликт. - Параметры не подставляются? Если
paramsне переданы, шаблон отображается как есть — это и есть тот самый буквальный%name%, который вы видите.
Всё работает, но ломается после того, как я несколько раз переключаю локали туда-обратно
Обновите SDK. Реальные исправленные ошибки в этой области: возврат к исходной локали (initial/SSR) оставлял активным предыдущий каталог (исправлено в langsys-js-typescript 0.2.2), а потребители Translate/Phrase для блоков контента могли зависать навсегда на каталоге, подготовленном сервером (исправлено в 0.2.3). Если вы видите строки устаревшей локали или блоки, которые никогда не загружаются, сверьте установленную версию с журналом изменений базового SDK, прежде чем отлаживать собственный код.
На стороне сервера: страница отвечает ошибкой 500 вместо плавной деградации
Клиент PHP SDK выбрасывает исключение, если создан без учётных данных, поэтому приложение на Laravel (или обычном PHP) с пустым LANGSYS_API_KEY даст сбой при разрешении контейнера, ещё до начала любого перевода. Задайте учётные данные (или, если приложение должно запускаться без них, привяжите клиент-заглушку, как это делают демо-приложения). Когда учётные данные заданы, сбои API во время работы не приводят к отказу: если API недоступен или отклоняет запрос, отображается исходный текст, а исключение фиксируется в отчёте — страница не падает.
На стороне сервера: первый запрос зависает, а потом всё работает быстро
Тайм-аут HTTP в PHP SDK составляет 30 секунд, и экземпляр API, выходящий из режима простоя, при самом первом вызове может приблизиться к этому пределу. Такой запрос либо завершится медленно (и прогреет кэш каталога — всё последующее отдаётся из кэша), либо завершится по тайм-ауту, и будет показан исходный текст. Если первая загрузка страницы после паузы идёт медленно, причина в этом: прогревается API, а не ваша интеграция.
На стороне сервера: я выполнил рендеринг с ключом записи, но фразы нет в панели управления
Найденные фразы ставятся в очередь во время запроса и отправляются после того, как ответ уже отдан — это terminable middleware в Laravel. Обновляйте дашборд спустя некоторое время после загрузки страницы, а не во время неё. В долгоживущих воркерах (Octane, FrankenPHP, RoadRunner) убедитесь, что используете интеграцию из комплекта SDK: обычный хук завершения работы PHP не срабатывает между запросами воркера.
Серверный рендеринг: переводы из одного запроса попадают в другой
Базовый JS SDK хранит каталог только одной локали одновременно — это синглтон уровня модуля. Сервер Node, который рендерит разные локали для параллельных запросов, столкнётся с состоянием гонки. Загружайте каталог для каждого запроса и инициализируйте им клиент, как в руководствах по Next.js, Nuxt и SvelteKit, либо запускайте по одной локали на процесс.
Всё ещё не получается? Создайте обращение в репозитории SDK — typescript, react, vue, svelte, php или laravel — и приложите вывод консоли (или журнал сервера) неудавшегося запроса. Либо напишите на [email protected].