Ir al contenido
Sonenta

SDK · Evaluación por el usuario final

Vista previa

@sonenta/feedback

Deja que tus propios usuarios finales puntúen (5★) y propongan traducciones desde tu app en producción. Un paquete, cinco puntos de entrada — /react, /native, /vue, /svelte, /core — la misma red, la misma sesión generada en el servidor, el mismo back office de moderación. React y React Native se conectan al provider @sonenta/*-i18n que ya usas (sin un segundo contexto, sin re-render del host); Vue y Svelte son adaptadores autónomos e idiomáticos. Disponible como add-on de pago a partir de Pro.

El paquete @sonenta/feedback se entrega con el add-on «Evaluación de traducciones por el usuario final» en el lanzamiento V1 de Sonenta. Todavía no está en npm — el contrato de red está congelado (v3); los bindings de los frameworks se están estabilizando y aún pueden cambiar antes del lanzamiento.

1. Instalar (en el lanzamiento)

Un solo paquete. Importa el punto de entrada de tu framework: @sonenta/feedback/react, /native (RN/Expo), /vue, /svelte, o /core para el resto. vue / svelte son peer deps opcionales — solo el punto de entrada correspondiente las necesita. Publicado con el add-on en el lanzamiento 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

Añade feedbackPlugin() al slot plugins de tu provider @sonenta/react-i18next (>= 0.7.0) existente — sin nuevo provider, sin un segundo contexto. El provider llama al setup() del plugin una vez y reutiliza sus propios apiBase / projectId / defaultLocale. El panel se monta como una hoja hermana aislada con un store privado de apertura/cierre: abrirlo nunca vuelve a renderizar tu árbol host. Dispáralo desde tu propio CTA mediante el controlador proporcionado por controllerRef (o el 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 las opciones de feedbackPlugin() / createFeedback()
Opción Tipo Por defecto
controllerRefRef<Controller>
onReady(c) => void
keysstring[]auto-discovered
flushDebounceMsnumber1500
maxBatchnumber20
defaultButtonbooleanfalse

3. React Native / Expo

Mismo esquema desde el punto de entrada /native: añade feedbackPlugin() al slot plugins del mismo provider @sonenta/react-i18next en tu app Expo y dispáralo mediante el controlador. Sin módulos nativos adicionales; el almacenamiento del token usa el secure store de la 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 es un adaptador autónomo — config explícita, sin provider i18n del que heredar. createFeedback(config) devuelve { client, isOpen, controller, FeedbackPanel }. Monta <FeedbackPanel /> una vez cerca de la raíz (hace Teleport a body) y llama a controller.open() desde tu CTA. El mismo estado de apertura aislado — nunca vuelve a renderizar tu 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 es headless e idiomático: createFeedback(config) devuelve stores de Svelte — isOpen (Writable), strings (Writable) — además de open(), close(), loadStrings(), rate(), suggest(). Tú renderizas tu propio panel a partir de los stores; el SDK gestiona el transporte, el consentimiento y la sesión del 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. El resto — /core

@sonenta/feedback/core expone el FeedbackClient congelado sobre el que se construyen todos los adaptadores: acceptTos(), loadStrings(), rate(), suggest(), transporte con debounce y batch, JWT rotativo. Úsalo directamente para cualquier framework sin 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. Acotación a las claves mostradas (automática)

El panel se acota automáticamente a las claves realmente mostradas en la vista actual, mediante el registro global de claves que produce el SDK @sonenta/*-i18n — sin configuración. Pasa un array keys explícito solo como respaldo (p. ej. cadenas que no provienen de @sonenta/*-i18n); nunca pases todo tu catálogo — eso expondría todas las cadenas de la app, no las que el usuario está mirando. El registro se rastrea al montar y se cuenta por referencia: las cadenas persistentes siempre en pantalla (un encabezado, un eyebrow) permanecen registradas mientras su componente esté montado — no hay reset por vista. (reset() existe únicamente como vía de escape para casos límite de enrutamiento no-React; el SDK nunca lo llama automáticamente.)

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)

Una pantalla que renderiza varios namespaces puede acotar el panel solo al que le interesa al cliente — pasa un namespace opcional (string | string[]) en el trigger/config (feedbackPlugin() para React/Native, createFeedback() para Vue/Svelte, resolveKeys() / filterByNamespace() para /core). Se compone después de la acotación a las claves mostradas — mostrado = mostrado ∩ namespace. Sin definir, "" o [] = ningún filtro (como antes). Nunca recae en todo el proyecto.

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. Sesión generada en el servidor (todos los frameworks)

La clave de sesión / agrupación se genera en el servidor al dar el consentimiento. El cliente nunca la envía ni la genera por sí mismo — no hay config groupingKey ni campo de petición. En acceptTos() el backend la devuelve (vinculada al JWT restringido); cada adaptador la expone en solo lectura mediante client.sessionId. Un endUserId recurrente conserva su valor de servidor estable; un nuevo usuario final obtiene un sess_… nuevo.

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. Consentimiento y seguridad (automático)

Antes de la primera escritura, el usuario final acepta los TdS de usuario final de Sonenta versionados. El SDK obtiene luego un JWT de corta duración limitado al scope feedback:write — criptográficamente separado de la autenticación de tu cliente — y lo renueva de forma transparente. Los usuarios finales son anónimos (id opaco, sin PII). No tienes que programar nada del lado de la seguridad.

Lo que obtienes gratis

Siguiente