SDK · Avaliação pelo utilizador final
Pré-visualização@sonenta/feedback
Deixe os seus próprios utilizadores finais classificarem (5★) e sugerirem traduções a partir da app em produção. Um pacote, cinco pontos de entrada — /react, /native, /vue, /svelte, /core — mesma rede, mesma sessão gerada no servidor, mesmo back office de moderação. React e React Native ligam-se ao provider @sonenta/*-i18n que já utiliza (sem segundo contexto, sem re-render do host); Vue e Svelte são adaptadores autónomos idiomáticos. Disponível como add-on pago a partir do Pro.
O pacote @sonenta/feedback é disponibilizado com o add-on «Avaliação de traduções pelo utilizador final» no lançamento V1 da Sonenta. Ainda não está no npm — o contrato de rede está congelado (v3); os bindings de frameworks estão a estabilizar e podem ainda evoluir antes do lançamento.
1. Instalar (no lançamento)
Um único pacote. Importe o ponto de entrada do seu framework: @sonenta/feedback/react, /native (RN/Expo), /vue, /svelte, ou /core para o resto. vue / svelte são peer deps opcionais — apenas o ponto de entrada correspondente precisa delas. Publicado com o add-on no lançamento 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) — plugin i18n
Adicione feedbackPlugin() ao slot plugins do seu provider @sonenta/react-i18next (>= 0.7.0) existente — sem novo provider, sem segundo contexto. O provider chama o setup() do plugin uma vez e reutiliza os seus próprios apiBase / projectId / defaultLocale. O painel é montado como uma folha irmã isolada com um store privado de abrir/fechar, pelo que abri-lo nunca faz re-render da sua árvore host. Despolete a partir do seu próprio CTA através do controlador fornecido por controllerRef (ou pelo callback 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> Todas as opções de feedbackPlugin() / createFeedback()
| Opção | Tipo | Por omissão |
|---|---|---|
| controllerRef | Ref<Controller> | — |
| onReady | (c) => void | — |
| keys | string[] | auto-discovered |
| flushDebounceMs | number | 1500 |
| maxBatch | number | 20 |
| defaultButton | boolean | false |
3. React Native / Expo
Mesmo padrão a partir do ponto de entrada /native: adicione feedbackPlugin() ao slot plugins do mesmo provider @sonenta/react-i18next na sua app Expo e despolete através do controlador. Sem módulos nativos adicionais; o armazenamento do token usa o secure store da plataforma.
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 é um adaptador autónomo — config explícita, sem provider i18n do qual herdar. createFeedback(config) devolve { client, isOpen, controller, FeedbackPanel }. Monte <FeedbackPanel /> uma vez perto da raiz (faz Teleport para body) e chame controller.open() a partir do seu próprio CTA. O mesmo estado de abertura isolado — nunca faz re-render da sua 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 e idiomático: createFeedback(config) devolve stores Svelte — isOpen (Writable), strings (Writable) — mais open(), close(), loadStrings(), rate(), suggest(). O painel é renderizado por si a partir dos stores; o SDK trata do transporte, do consentimento e da sessão de servidor.
+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. O resto — /core
@sonenta/feedback/core expõe o FeedbackClient congelado sobre o qual todos os adaptadores são construídos: acceptTos(), loadStrings(), rate(), suggest(), transporte com debounce e em lote, JWT rotativo. Use-o diretamente para qualquer framework sem adaptador 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. Delimitação às chaves apresentadas (automática)
O painel delimita-se automaticamente às chaves efetivamente apresentadas na vista atual, através do registo global de chaves produzido pelo SDK @sonenta/*-i18n — sem configuração. Passe um array keys explícito apenas como recurso de reserva (p. ex. strings que não provêm de @sonenta/*-i18n); nunca passe todo o seu catálogo — isso exporia todas as strings da app, e não as que o utilizador está a ver. O registo é rastreado no montar e contado por referência: strings persistentes sempre no ecrã (um cabeçalho, um eyebrow) permanecem registadas enquanto o seu componente estiver montado — não há reset por vista. (reset() existe apenas como válvula de escape para casos limite de routing não-React; o SDK nunca o chama automaticamente.)
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. Filtro de namespace (opcional)
Um ecrã que apresenta vários namespaces pode delimitar o painel apenas àquele que interessa ao cliente — passe um namespace opcional (string | string[]) no trigger/config (feedbackPlugin() para React/Native, createFeedback() para Vue/Svelte, resolveKeys() / filterByNamespace() para /core). Compõe-se depois da delimitação às chaves apresentadas — apresentado = apresentado ∩ namespace. Por definir, "" ou [] = sem filtro (como antes). Nunca recorre a todo o projeto.
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. Sessão gerada no servidor (todos os frameworks)
A chave de sessão / agrupamento é gerada no servidor no momento do consentimento. O cliente nunca a envia nem a gera — não há config groupingKey nem campo de pedido. No acceptTos() o backend devolve-a (ligada ao JWT restrito); cada adaptador expõe-na em apenas leitura através de client.sessionId. Um endUserId recorrente mantém o seu valor estável de servidor; um novo utilizador final obtém um sess_… novo.
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. Consentimento e segurança (automático)
Antes da primeira escrita, o utilizador final aceita os Termos de Utilização para utilizador final da Sonenta, versionados. O SDK obtém então um JWT de curta duração limitado ao scope feedback:write — criptograficamente separado da autenticação do seu cliente — e renova-o de forma transparente. Os utilizadores finais são anónimos (id opaco, sem PII). Não tem nada a programar do lado da segurança.
O que obtém gratuitamente
- Zero re-render do host. React/Native correm como folha irmã isolada do provider i18n; Vue/Svelte mantêm o estado de abertura no seu próprio store. Adicionar ou abrir o feedback nunca faz re-render da sua app.
- Consentimento + sessão de servidor tratados. Aceitação dos Termos de Utilização, sessão gerada no servidor, obtenção do JWT, refresh rotativo e um único retry transparente em 401 — tudo dentro do SDK. Nunca lança exceções no seu caminho de render.
- Transporte com debounce e em lote. Classificações e sugestões são enfileiradas e enviadas num debounce (1,5s por omissão), num lote máximo, ou ao fechar. Best-effort: um lote falhado é reenfileirado uma vez e depois absorvido.
- Moderação antes da publicação. Nada submetido por um utilizador final fica em direto automaticamente. As sugestões chegam como
pendingna fila de moderação do seu dashboard — aprova, rejeita ou aplica através do caminho de edição auditado habitual. As classificações agregam-se num dashboard em tempo real por chave / por idioma.