SDK · 终端用户评估
预览版@sonenta/feedback
让你自己的终端用户在已上线的 app 内为翻译评分(5★)并提出建议。一个包,五个入口——/react、/native、/vue、/svelte、/core——相同的 wire、相同的服务端签发 session、相同的审核后台。React 与 React Native 接入你已经在用的 @sonenta/*-i18n provider(无需第二个 context,不会触发宿主重新渲染);Vue 与 Svelte 是符合各自惯用法的独立 adapter。作为 Pro 及以上的付费 add-on 提供。
@sonenta/feedback 包随「终端用户翻译评估」add-on 在 Sonenta V1 发布时一同推出。它尚未发布到 npm——wire 契约已冻结(v3);框架 binding 仍在稳定中,在发布前可能还会变化。
1. 安装(发布时)
只有一个包。导入你所用框架的入口:@sonenta/feedback/react、/native(RN/Expo)、/vue、/svelte,或为其余情况导入 /core。vue / svelte 是可选的 peer deps——只有对应的入口才需要它们。随 add-on 在 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 plugin
将 feedbackPlugin() 添加到你现有 @sonenta/react-i18next(>= 0.7.0)provider 的 plugins 槽位——无需新 provider,无需第二个 context。provider 会调用 plugin 的 setup() 一次,并复用它自身的 apiBase / projectId / defaultLocale。面板会作为一个隔离的兄弟叶子节点挂载,并拥有私有的开/关 store,因此打开它永远不会重新渲染你的宿主树。通过 controllerRef 提供的 controller(或 onReady 回调)从你自己的 CTA 触发。
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() 选项
| 选项 | 类型 | 默认值 |
|---|---|---|
| controllerRef | Ref<Controller> | — |
| onReady | (c) => void | — |
| keys | string[] | auto-discovered |
| flushDebounceMs | number | 1500 |
| maxBatch | number | 20 |
| defaultButton | boolean | false |
3. React Native / Expo
从 /native 入口采用相同模式:在你的 Expo app 中,将 feedbackPlugin() 添加到同一个 @sonenta/react-i18next provider 的 plugins 槽位,并通过 controller 触发。无需额外的原生模块;token 存储使用平台的 secure store。
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 是一个独立 adapter——显式配置,没有可继承的 i18n provider。createFeedback(config) 返回 { client, isOpen, controller, FeedbackPanel }。在靠近根部处挂载一次 <FeedbackPanel />(它会 Teleport 到 body),并从你自己的 CTA 调用 controller.open()。同样的隔离开启状态——它永远不会重新渲染你的 app。
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) 返回 Svelte store——isOpen(Writable)、strings(Writable)——以及 open()、close()、loadStrings()、rate()、suggest()。你从这些 store 渲染你自己的面板;SDK 负责传输、consent 和服务端 session。
+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 暴露了所有 adapter 都构建于其上的、已冻结的 FeedbackClient:acceptTos()、loadStrings()、rate()、suggest()、debounce 加批处理的传输、轮换的 JWT。对于任何没有 first-party adapter 的框架,可直接使用它。
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. 已渲染 key 的范围限定(自动)
面板会通过 @sonenta/*-i18n SDK 生成的全局 key 注册表,自动将范围限定到当前视图上实际渲染的 key——无需配置。仅在回退场景下才传入显式的 keys 数组(例如并非来自 @sonenta/*-i18n 的字符串);切勿传入你的整个 catalogue——那会暴露 app 中的每一个字符串,而非用户正在查看的那些。该注册表是按挂载追踪并按引用计数的:始终显示在屏幕上的持久字符串(页眉、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 的屏幕可以将面板范围限定到客户关心的那一个——在 trigger/config 上传入一个可选的 namespace(string | string[])(React/Native 用 feedbackPlugin(),Vue/Svelte 用 createFeedback(),/core 用 resolveKeys() / filterByNamespace())。它在已渲染范围限定之后组合——显示项 = 已渲染 ∩ 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. 服务端签发 session(所有框架)
session / 分组 key 是在 consent 时由服务端签发的。客户端永远不会发送或自行生成它——既没有 groupingKey 配置,也没有请求字段。在 acceptTos() 时后端会返回它(绑定到受限的 JWT 中);每个 adapter 都通过 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. consent 与安全(自动)
在首次写入之前,终端用户会接受带版本号的 Sonenta 终端用户 ToS。随后 SDK 会获取一个短期的、仅限 feedback:write scope 的 JWT——与你的客户 auth 在密码学上相互分离——并透明地轮换它。终端用户是匿名的(不透明 id,无 PII)。安全方面你无需编写任何代码。
你免费获得的能力
- 零宿主重新渲染。 React/Native 作为 i18n provider 的隔离兄弟叶子节点运行;Vue/Svelte 将开启状态保存在它们自己的 store 中。添加或打开 feedback 永远不会重新渲染你的 app。
- consent + 服务端 session 已为你处理。 ToS 接受、服务端签发 session、JWT 获取、轮换刷新,以及在 401 时一次性的透明重试——全部都在 SDK 内部。它永远不会向你的渲染路径抛出异常。
- debounce 且批处理的传输。 评分和建议会被排队,并在 debounce(默认 1.5 秒)时、达到批处理上限时或关闭时刷新发送。尽力而为:失败的批次会被重新排队一次,然后被吞掉。
- 发布前审核。 终端用户提交的任何内容都不会自动上线。建议会以
pending状态进入你 dashboard 的审核队列——你可以通过正常的、有审计记录的编辑路径来批准、拒绝或应用它们。评分则汇总进一个按 key / 按语言的实时仪表板。