Перейти к содержимому

SDK и клиенты

Здесь два разных вещи называются SDK, и решают они разные задачи.

  • Рантайм-клиенты забирают переводы из OTA-бандла и разрешают ключи в вашем приложении. Есть для Go, Rust и NestJS.
  • Браузерный SDK ничего не забирает: он помечает элементы DOM ключом, из которого они получились, чтобы расширение Chrome могло показать и отредактировать перевод поверх работающего продукта.

Все три работают одинаково: укажите источник — клиент загрузит его в память, будет обновлять в фоне каждые пять минут и разрешать ключ по пространству имён и локали.

Источников два:

  • OTA — один или несколько опубликованных бандлов по ключу доступа. Бандлу можно дать префикс, который станет его пространством имён; несколько бандлов с одним префиксом сливаются, а при конфликте ключей побеждает последний.
  • Map — карта переводов проекта по идентификатору проекта и ключу API. Здесь пространством имён становится имя файла.
Окно терминала
go get github.com/OwnLate/go-client
client, 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: загружает бандл при старте и обновляет его в фоне.

Клиенты договорились о правилах — именно поэтому отсутствующий перевод безобиден:

  1. Локаль берётся из вызова, иначе из конфигурации клиента.
  2. Ищется пространство имён; для OTA-источника неизвестное пространство сводится к бандлу по умолчанию.
  3. Отсутствующая локаль подменяется локалью того же языка — en_US дотягивается до en и обратно, — а если и так не вышло, первой по алфавиту, чтобы выбор не прыгал от вызова к вызову.
  4. Неизвестный ключ возвращается таким, каким его запросили, и никогда пустой строкой.
  5. Подстановки вида {{name}} заменяются переданными значениями.

Оба клиента читают снимок под блокировкой и на обновлении меняют снимок целиком, поэтому их безопасно шарить между горутинами и задачами.

@ownlate/sdk размечает DOM. Именно он превращает работающее приложение в то, что переводчик может править по месту.

Окно терминала
npm install @ownlate/sdk
import { 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)
})
  1. init() сообщает о себе через postMessage, это ловит content script расширения.
  2. wrapT() и annotateElement() проставляют узлам атрибут data-ownlate-key.
  3. Расширение читает эти атрибуты и показывает соответствующие сегменты в боковой панели.
  4. Правка в панели сохраняется черновиком на сегменте и отправляется обратно на страницу, поэтому интерфейс обновляется сразу.

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

Браузерный SDK авторизуется SDK-токеном, а не ключом API. Токены создаются для каждого проекта на вкладке SDK, и каждый из них:

  • показывается один раз, при создании, дальше — только префиксом;
  • дотягивается ровно до одного проекта, того, для которого создан;
  • умеет читать сегменты по ключам и сохранять черновики переводов — и больше ничего;
  • запоминает, когда его использовали последний раз, и отзывается в любой момент.

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