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

SDK · Оценка конечным пользователем

Превью

@sonenta/feedback

Позвольте вашим собственным конечным пользователям оценивать (5★) и предлагать переводы прямо из вашего продакшен-приложения. Один пакет, пять точек входа — /react, /native, /vue, /svelte, /core — одна и та же сеть, одна и та же сессия, выпущенная на сервере, один и тот же бэк-офис модерации. React и React Native подключаются к провайдеру @sonenta/*-i18n, который вы уже используете (никакого второго контекста, никаких ре-рендеров хоста); Vue и Svelte — самостоятельные идиоматичные адаптеры. Доступно как платное дополнение начиная с Pro.

Пакет @sonenta/feedback поставляется с дополнением «Оценка переводов конечным пользователем» в момент запуска Sonenta V1. Его ещё нет в npm — сетевой контракт зафиксирован (v3); привязки к фреймворкам стабилизируются и ещё могут измениться до запуска.

1. Установка (при запуске)

Один пакет. Импортируйте точку входа вашего фреймворка: @sonenta/feedback/react, /native (RN/Expo), /vue, /svelte или /core для всего остального. vue / svelte — это опциональные peer-зависимости; они нужны только соответствующей точке входа. Публикуется вместе с дополнением при запуске V1.

terminal
1// ships with the End-user evaluation add-on at the V1 launch2npm i @sonenta/react-i18next @sonenta/feedback3// feedback peers with @sonenta/react-i18next 2.x4// optional peer deps only for the matching entry: vue · svelte

2. React (web) — плагин i18n

Добавьте feedbackPlugin() в слот plugins вашего существующего провайдера @sonenta/react-i18next (>= 0.7.0) — никакого нового провайдера, никакого второго контекста. Провайдер вызывает setup() плагина один раз и переиспользует свои apiBase / projectId / defaultLocale. Панель монтируется как изолированный соседний лист с приватным хранилищем открытия/закрытия: её открытие никогда не вызывает ре-рендер вашего хост-дерева. Запускайте из своего собственного CTA через контроллер, предоставленный через controllerRef (или коллбэк onReady).

main.tsx
1// src/main.tsx — plugin of the i18n provider you already run2import { SonentaProvider } from "@sonenta/react-i18next";3import { feedbackPlugin } from "@sonenta/feedback/react";4import { useRef } from "react"; 6const feedback = useRef(null); 8<SonentaProvider9  projectUuid="proj_xxx"10  token={import.meta.env.VITE_SONENTA_TOKEN}11  plugins={[ feedbackPlugin({ controllerRef: feedback }) ]}12>13  <App />14</SonentaProvider> 16// own CTA — does NOT re-render the host tree17<button onClick={() => feedback.current?.open()}>Rate translations</button>
Все опции feedbackPlugin() / createFeedback()
Опция Тип По умолчанию
controllerRefRef<Controller>
onReady(c) => void
keysstring[]auto-discovered
flushDebounceMsnumber1500
maxBatchnumber20
defaultButtonbooleanfalse

3. React Native / Expo

Та же схема из точки входа /native: добавьте feedbackPlugin() в слот plugins того же провайдера @sonenta/react-i18next в вашем приложении Expo и запускайте через контроллер. Никаких дополнительных нативных модулей; хранение токена использует защищённое хранилище платформы.

App.tsx
1// App.tsx (Expo / React Native) — same plugins slot2import { SonentaProvider } from "@sonenta/react-i18next";3import { feedbackPlugin } from "@sonenta/feedback/native"; 5<SonentaProvider6  projectUuid="proj_xxx"7  token={process.env.EXPO_PUBLIC_SONENTA_TOKEN}8  plugins={[ feedbackPlugin({ onReady: (c) => (ctrl = c) }) ]}9>{/* … */}</SonentaProvider> 11// wire ctrl.open() to your own button / FAB

4. Vue

@sonenta/feedback/vue — самостоятельный адаптер: явная конфигурация, нет провайдера i18n, от которого нужно наследоваться. createFeedback(config) возвращает { client, isOpen, controller, FeedbackPanel }. Смонтируйте <FeedbackPanel /> один раз рядом с корнем (он использует Teleport в body) и вызывайте controller.open() из вашего собственного CTA. То же изолированное состояние открытия — оно никогда не вызывает ре-рендер вашего приложения.

App.vue
1// main.ts — standalone adapter, explicit config2import { createFeedback } from "@sonenta/feedback/vue"; 4export const { controller, FeedbackPanel } = createFeedback({5  apiBase: "https://api.sonenta.com",6  projectId: "proj_xxx", language: "fr",7}); 9// App.vue — mount once near root (Teleports to body)10<FeedbackPanel />11<button @click="controller.open()">Rate translations</button>

5. Svelte

@sonenta/feedback/svelte — headless и идиоматичный: createFeedback(config) возвращает store-ы Svelte — isOpen (Writable), strings (Writable) — плюс open(), close(), loadStrings(), rate(), suggest(). Вы рисуете собственную панель из store-ов; SDK берёт на себя транспорт, согласие и серверную сессию.

+page.svelte
1// feedback.ts — headless idiomatic stores2import { createFeedback } from "@sonenta/feedback/svelte"; 4export const fb = createFeedback({5  apiBase: "https://api.sonenta.com",6  projectId: "proj_xxx", language: "fr",7}); 9// component — render your own panel from the stores10{#if $fb.isOpen}{#each $fb.strings as s}…{/each}{/if}11<button on:click={fb.open}>Rate translations</button>

6. Всё остальное — /core

@sonenta/feedback/core предоставляет зафиксированный FeedbackClient, на котором построены все адаптеры: acceptTos(), loadStrings(), rate(), suggest(), транспорт с дебаунсом и батчингом, ротируемый JWT. Используйте его напрямую для любого фреймворка без first-party адаптера.

feedback.ts
1// any framework — the frozen client all adapters wrap2import { FeedbackClient } from "@sonenta/feedback/core"; 4const client = new FeedbackClient({5  apiBase: "https://api.sonenta.com",6  projectId: "proj_xxx", language: "fr",7});8await client.acceptTos();   // server mints the session9await client.loadStrings(); client.rate(/* … */); client.suggest(/* … */);

7. Привязка к отображаемым ключам (автоматически)

Панель автоматически ограничивается ключами, фактически отображёнными в текущем представлении, через глобальный реестр ключей, который формирует SDK @sonenta/*-i18n — без настройки. Передавайте явный массив keys только как запасной вариант (например, строки не из @sonenta/*-i18n); никогда не передавайте весь ваш каталог — это раскрыло бы все строки приложения, а не те, на которые смотрит пользователь. Реестр отслеживается по монтированию и считается по ссылкам: постоянно присутствующие на экране строки (заголовок, eyebrow) остаются зарегистрированными, пока их компонент смонтирован — никакого сброса на каждое представление. (reset() существует только как аварийный выход для краевых случаев маршрутизации вне React; SDK никогда не вызывает его автоматически.)

scoping.ts
1// the panel auto-scopes to keys RENDERED on the current2// view, via the global key registry the @sonenta/*-i18n3// SDK produces — no config needed:4feedbackPlugin({ controllerRef: feedback });   // auto-scoped 6// explicit keys = FALLBACK only (e.g. strings not from7// @sonenta/*-i18n). NEVER pass your whole catalogue.8feedbackPlugin({ keys: ["common:checkout.cta"] });

8. Фильтр по namespace (опционально)

Экран, который рендерит несколько namespace, может ограничить панель именно тем, который интересует клиента — передайте опциональный namespace (string | string[]) в триггер/конфигурацию (feedbackPlugin() для React/Native, createFeedback() для Vue/Svelte, resolveKeys() / filterByNamespace() для /core). Он применяется после привязки к отображаемым ключам — показано = отображённое ∩ namespace. Не задано, "" или [] = без фильтра (как раньше). Он никогда не откатывается на весь проект.

namespace.ts
1// §0d — OPTIONAL namespace filter (customer feature).2// Composes AFTER rendered-scoping: shown = rendered ∩ namespace.3feedbackPlugin({ controllerRef: feedback, namespace: "quiz" }); 5// Vue / Svelte — same option on createFeedback:6createFeedback({ apiBase, projectId, language, namespace: ["quiz"] }); 8// /core — resolveKeys / filterByNamespace:9resolveKeys(explicit, "quiz");   // or filterByNamespace(keys, "quiz") 11// unset / "" / [] ⇒ no filter (identical to v5).

9. Сессия, выпущенная на сервере (все фреймворки)

Ключ сессии / группировки выпускается на стороне сервера при согласии. Клиент никогда не отправляет и не генерирует его сам — нет конфигурации groupingKey и нет поля запроса. При acceptTos() бэкенд возвращает его (привязанным к ограниченному JWT); каждый адаптер предоставляет его только для чтения через client.sessionId. Возвращающийся endUserId сохраняет своё стабильное серверное значение; новый пользователь получает свежий sess_….

consent.ts
1// session/grouping key is MINTED SERVER-SIDE at consent2await client.acceptTos();      // POST /v1/feedback/tos3client.sessionId;              // read-only, e.g. "sess_018f…" 5// NO groupingKey config, NO request field —6// the client never sends or self-generates the session.

10. Согласие и безопасность (автоматически)

Перед первой записью конечный пользователь принимает версионированные пользовательские условия Sonenta. Затем SDK получает короткоживущий JWT, ограниченный скоупом feedback:write — криптографически отделённый от вашей клиентской авторизации — и обновляет его прозрачно. Конечные пользователи анонимны (непрозрачный id, без PII). Со стороны безопасности вам ничего писать не нужно.

Что вы получаете бесплатно

Далее