SDKs and clients
There are two different things called an SDK here, and they solve different problems.
- Runtime clients fetch translations from an OTA bundle and resolve keys in your application. Available for Go, Rust and NestJS.
- The browser SDK does not fetch anything: it tags DOM elements with the key that produced them, so the Chrome extension can show and edit the translation on top of your running product.
Runtime clients
Section titled “Runtime clients”All three work the same way: point them at a source, they load it into memory, refresh it in the background every five minutes, and resolve a key by namespace and locale.
Two sources are available:
- OTA — one or more published bundles, addressed by access key. Each bundle can be given a prefix, which becomes its namespace; several bundles sharing a prefix are merged, and on a key collision the last one wins.
- Map — the translations map of a project, addressed by project ID and API key. Here the file name becomes the namespace.
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 refreshes in the background and retries on failure; Load does a single load and returns the error instead.
[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 is cheap to clone — every clone shares the same translations and the same refresh task. Dropping the returned handle stops the refresh.
NestJS
Section titled “NestJS”npm install @globalart/ownlate-nestjs-translatorThe module wraps the same behaviour for a Nest application, loading a bundle at start-up and refreshing it in the background.
How a key is resolved
Section titled “How a key is resolved”The clients agree on the rules, which are what makes a missing translation harmless:
- The locale comes from the call, otherwise from the client’s configuration.
- The namespace is looked up; for an OTA source an unknown namespace falls back to the default bundle.
- A missing locale falls back to one of the same language —
en_USreachesenand back — and failing that to the first locale alphabetically, so the choice stays stable between calls. - An unknown key is returned as it was asked for, never as an empty string.
- Placeholders written
{{name}}are substituted from the values you pass.
Both clients read a snapshot under a lock and swap the whole snapshot on refresh, so they are safe to share across goroutines or tasks.
The browser SDK
Section titled “The browser SDK”@ownlate/sdk annotates the DOM. It is what turns your running application into something a translator can edit in place.
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',})Wrap your i18n function once, and every string it renders is tagged:
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>}| Export | Signature | What it does |
|---|---|---|
init | (config) => void | Start the SDK. Call once. |
wrapT | (t) => t | Wrap an i18n t() so rendered nodes are annotated |
annotateElement | (el, key) => void | Annotate a node by hand |
onTranslationUpdate | (cb) => unsubscribe | React to an edit made in the extension |
destroy | () => void | Remove listeners and reset |
Use annotateElement where wrapT cannot see the node — a placeholder, an aria-label, a title attribute:
annotateElement(document.querySelector('#search'), 'search.placeholder')And apply live edits without a reload:
onTranslationUpdate(({ key, value, lang }) => { i18next.addResource(lang, 'translation', key, value)})How it fits together
Section titled “How it fits together”init()announces itself with apostMessage, which the extension’s content script picks up.wrapT()andannotateElement()set adata-ownlate-keyattribute on the nodes.- The extension reads those attributes and shows the matching segments in its side panel.
- An edit in the panel is saved as a draft on the segment, and posted back to the page so your UI updates immediately.
Because edits land as drafts, in-context editing never bypasses review.
SDK tokens
Section titled “SDK tokens”The browser SDK authenticates with an SDK token, not an API key. Tokens are created per project on the project’s SDK tab, and each one:
- is shown once, at creation, and afterwards only by its prefix;
- reaches exactly one project — the one it was created for;
- can read segments by key and save draft translations, and nothing else;
- records when it was last used, and can be revoked at any time.
That narrowness is the point: the token ships in a browser bundle, so it must not be able to do anything you would mind a stranger doing.