SDK и клиенты
Здесь два разных вещи называются SDK, и решают они разные задачи.
- Рантайм-клиенты забирают переводы из OTA-бандла и разрешают ключи в вашем приложении. Есть для Go, Rust и NestJS.
- Браузерный SDK ничего не забирает: он помечает элементы DOM ключом, из которого они получились, чтобы расширение Chrome могло показать и отредактировать перевод поверх работающего продукта.
Рантайм-клиенты
Заголовок раздела «Рантайм-клиенты»Все три работают одинаково: укажите источник — клиент загрузит его в память, будет обновлять в фоне каждые пять минут и разрешать ключ по пространству имён и локали.
Источников два:
- OTA — один или несколько опубликованных бандлов по ключу доступа. Бандлу можно дать префикс, который станет его пространством имён; несколько бандлов с одним префиксом сливаются, а при конфликте ключей побеждает последний.
- Map — карта переводов проекта по идентификатору проекта и ключу API. Здесь пространством имён становится имя файла.
go get github.com/OwnLate/go-clientclient, err := ownlate.New(ownlate.Config{ Source: ownlate.OTASource{Bundles: []ownlate.OTABundle{{AccessKey: accessKey}}}, Locale: "ru",})if err != nil { return err}defer client.Close()
client.Start(ctx)<-client.Ready()
client.T("notification.title", "en_US")client.Translate("emails", "greeting", map[string]any{"name": "Roman"}, "ru")Start обновляет в фоне и повторяет попытки при неудаче; Load делает одну загрузку и возвращает ошибку.
[dependencies]ownlate = { git = "https://github.com/OwnLate/rust-client" }let client = ownlate::Client::ota(access_key, "en_US")?;
let refresh = client.start();client.ready().await;
client.t("notification.title", "en_US");client.translate("emails", "subject", Some(&json!({ "plan": "Pro" })), "en_US");Client дёшево клонируется — все клоны разделяют одни переводы и одну фоновую задачу обновления. Если уронить возвращённый handle, обновление остановится.
npm install @globalart/ownlate-nestjs-translatorМодуль оборачивает то же поведение для приложения на Nest: загружает бандл при старте и обновляет его в фоне.
Как разрешается ключ
Заголовок раздела «Как разрешается ключ»Клиенты договорились о правилах — именно поэтому отсутствующий перевод безобиден:
- Локаль берётся из вызова, иначе из конфигурации клиента.
- Ищется пространство имён; для OTA-источника неизвестное пространство сводится к бандлу по умолчанию.
- Отсутствующая локаль подменяется локалью того же языка —
en_USдотягивается доenи обратно, — а если и так не вышло, первой по алфавиту, чтобы выбор не прыгал от вызова к вызову. - Неизвестный ключ возвращается таким, каким его запросили, и никогда пустой строкой.
- Подстановки вида
{{name}}заменяются переданными значениями.
Оба клиента читают снимок под блокировкой и на обновлении меняют снимок целиком, поэтому их безопасно шарить между горутинами и задачами.
Браузерный SDK
Заголовок раздела «Браузерный SDK»@ownlate/sdk размечает DOM. Именно он превращает работающее приложение в то, что переводчик может править по месту.
npm install @ownlate/sdkimport { init, wrapT } from '@ownlate/sdk'
init({ projectId: 'your-project-id', workspaceId: 'your-workspace-id', apiKey: 'sdk_…', apiUrl: 'https://api.ownlate.com',})Оберните свою i18n-функцию один раз — и каждая отрисованная ею строка размечается:
import { useTranslation } from 'react-i18next'import { wrapT } from '@ownlate/sdk'
function Title() { const { t: rawT } = useTranslation() const t = wrapT(rawT)
return <h1>{t('home.title')}</h1>}| Экспорт | Сигнатура | Что делает |
|---|---|---|
init | (config) => void | Запускает SDK. Вызывается один раз. |
wrapT | (t) => t | Оборачивает t() так, чтобы отрисованные узлы размечались |
annotateElement | (el, key) => void | Разметить узел вручную |
onTranslationUpdate | (cb) => unsubscribe | Реагировать на правку, сделанную в расширении |
destroy | () => void | Снять слушатели и сбросить состояние |
annotateElement нужен там, где wrapT узел не видит, — placeholder, aria-label, атрибут title:
annotateElement(document.querySelector('#search'), 'search.placeholder')И применяйте живые правки без перезагрузки:
onTranslationUpdate(({ key, value, lang }) => { i18next.addResource(lang, 'translation', key, value)})Как всё складывается
Заголовок раздела «Как всё складывается»init()сообщает о себе черезpostMessage, это ловит content script расширения.wrapT()иannotateElement()проставляют узлам атрибутdata-ownlate-key.- Расширение читает эти атрибуты и показывает соответствующие сегменты в боковой панели.
- Правка в панели сохраняется черновиком на сегменте и отправляется обратно на страницу, поэтому интерфейс обновляется сразу.
Поскольку правки ложатся черновиками, редактирование по месту не обходит проверку.
SDK-токены
Заголовок раздела «SDK-токены»Браузерный SDK авторизуется SDK-токеном, а не ключом API. Токены создаются для каждого проекта на вкладке SDK, и каждый из них:
- показывается один раз, при создании, дальше — только префиксом;
- дотягивается ровно до одного проекта, того, для которого создан;
- умеет читать сегменты по ключам и сохранять черновики переводов — и больше ничего;
- запоминает, когда его использовали последний раз, и отзывается в любой момент.
Эта узость и есть смысл: токен уезжает в браузерную сборку, поэтому он не должен уметь ничего такого, что вам было бы неприятно увидеть в чужих руках.