SDK · Avaliação pelo utilizador final
Pré-visualização@sonenta/feedback
Deixe os seus próprios utilizadores finais avaliarem (5★) e sugerirem traduções a partir da sua app em produção. React e React Native ligam-se ao provider @sonenta/*-i18n que já utiliza (sem segundo contexto, sem re-render do host); tudo o resto passa por /core, o cliente congelado que todos os bindings envolvem. A mesma rede, a mesma sessão gerada no servidor, o mesmo back office de moderação. Disponível como add-on pago a partir do Pro.
O pacote @sonenta/feedback é fornecido com o add-on de avaliação de traduções pelo utilizador final no lançamento V1 da Sonenta. O contrato de rede está congelado (v3); os bindings de framework ainda estão a estabilizar e podem mudar 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. 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.dev",6 projectId: "proj_xxx", language: "fr",7 // REQUIRED. Without it, the first authenticated call8 // accepts the end-user ToS on your user's behalf.9 autoAcceptTos: false,10}); 12// show YOUR ToS step, and only once the user agrees:13await client.acceptTos(); // server mints the session14await client.loadStrings(); client.rate(/* … */); client.suggest(/* … */); 16// with autoAcceptTos:false, an unconsented authed call throws17// FeedbackError("not consented") instead of fabricating a record. 5. 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"] }); 6. 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). 7. 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. 8. Consentimento e segurança
Uma versão datada dos termos de utilizador final da Sonenta condiciona a primeira escrita. Nos painéis React e React Native, o passo dos termos é apresentado antes de qualquer chamada autenticada, e é o toque do próprio utilizador final que invoca acceptTos(): o carregamento das strings está condicionado ao consentimento por construção. Se optar por /core, construa o cliente com autoAcceptTos: false e chame acceptTos() apenas depois de o seu utilizador concordar, caso contrário a primeira chamada autenticada aceita em nome dele. O cliente passa então a deter um JWT de curta duração limitado ao scope feedback:write, criptograficamente separado da autenticação dos seus clientes, e renova-o de forma transparente. Os utilizadores finais são anónimos (id opaco, sem PII). No @sonenta/feedback 1.2.x e anteriores este é o comportamento POR OMISSÃO: o cliente aceita na primeira chamada autenticada, a menos que passe autoAcceptTos: false. A versão 1.3.0 inverte esse valor por omissão e falha de forma segura, lançando FeedbackError("not consented") em vez de aceitar em nome do seu utilizador.
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.