@sonenta/feedback
Laissez vos propres utilisateurs finaux noter (5★) et proposer des traductions depuis votre app en production. Un paquet, cinq points d'entrée, /react, /native, /vue, /svelte, /core, même réseau, même session générée côté serveur, même back-office de modération. React et React Native se branchent sur le provider @sonenta/*-i18n que vous utilisez déjà (pas de second contexte, aucun re-render hôte) ; Vue et Svelte sont des adaptateurs autonomes idiomatiques. Disponible en add-on payant à partir de Pro.
1. Installer
Section intitulée « 1. Installer »Un seul paquet. Importez le point d'entrée de votre framework : @sonenta/feedback/react, /native (RN/Expo), /vue, /svelte, ou /core pour le reste. vue / svelte sont des peer deps optionnelles, seul le point d'entrée concerné en a besoin.
npm i @sonenta/react-i18next @sonenta/feedback// optional peer deps only for the matching entry: vue · svelte2. React (web), plugin i18n
Section intitulée « 2. React (web), plugin i18n »Ajoutez feedbackPlugin() au slot plugins de votre provider @sonenta/react-i18next (>= 0.7.0) existant, aucun nouveau provider, aucun second contexte. Le provider appelle le setup() du plugin une fois et réutilise ses apiBase / projectId / defaultLocale. Le panneau se monte en feuille sœur isolée avec un store privé d'ouverture/fermeture : l'ouvrir ne re-render jamais votre arbre hôte. Déclenchez depuis votre propre CTA via le contrôleur fourni par controllerRef (ou le callback onReady).
// src/main.tsx: plugin of the i18n provider you already runimport { SonentaProvider } from "@sonenta/react-i18next";import { feedbackPlugin } from "@sonenta/feedback/react";import { useRef } from "react";
const feedback = useRef(null);
<SonentaProvider projectUuid="proj_xxx" token={import.meta.env.VITE_SONENTA_TOKEN} plugins={[ feedbackPlugin({ controllerRef: feedback }) ]}> <App /></SonentaProvider>
// own CTA, does NOT re-render the host tree<button onClick={() => feedback.current?.open()}>Rate translations</button>Toutes les options de feedbackPlugin() / createFeedback()
| Option | Type | Défaut |
|---|---|---|
controllerRef | Ref<Controller> | - |
onReady | (c) => void | - |
keys | string[] | auto-discovered |
flushDebounceMs | number | 1500 |
maxBatch | number | 20 |
defaultButton | boolean | false |
3. React Native / Expo
Section intitulée « 3. React Native / Expo »Même schéma depuis le point d'entrée /native : ajoutez feedbackPlugin() au slot plugins du même provider @sonenta/react-i18next dans votre app Expo et déclenchez via le contrôleur. Aucun module natif supplémentaire ; le stockage du token utilise le secure store de la plateforme.
// App.tsx (Expo / React Native): same plugins slotimport { SonentaProvider } from "@sonenta/react-i18next";import { feedbackPlugin } from "@sonenta/feedback/native";
<SonentaProvider projectUuid="proj_xxx" token={process.env.EXPO_PUBLIC_SONENTA_TOKEN} plugins={[ feedbackPlugin({ onReady: (c) => (ctrl = c) }) ]}>{/* … */}</SonentaProvider>
// wire ctrl.open() to your own button / FAB@sonenta/feedback/vue est un adaptateur autonome, config explicite, aucun provider i18n dont hériter. createFeedback(config) retourne { client, isOpen, controller, FeedbackPanel }. Montez <FeedbackPanel /> une fois près de la racine (Teleport vers body) et appelez controller.open() depuis votre CTA. Même état d'ouverture isolé, aucun re-render de votre app.
// main.ts: standalone adapter, explicit configimport { createFeedback } from "@sonenta/feedback/vue";
export const { controller, FeedbackPanel } = createFeedback({ apiBase: "https://api.sonenta.com", projectId: "proj_xxx", language: "fr",});
// App.vue: mount once near root (Teleports to body)<FeedbackPanel /><button @click="controller.open()">Rate translations</button>5. Svelte
Section intitulée « 5. Svelte »@sonenta/feedback/svelte est headless et idiomatique : createFeedback(config) retourne des stores Svelte, isOpen (Writable), strings (Writable), plus open(), close(), loadStrings(), rate(), suggest(). Vous rendez votre propre panneau depuis les stores ; le SDK gère le transport, le consentement et la session serveur.
// feedback.ts: headless idiomatic storesimport { createFeedback } from "@sonenta/feedback/svelte";
export const fb = createFeedback({ apiBase: "https://api.sonenta.com", projectId: "proj_xxx", language: "fr",});
// component: render your own panel from the stores{#if $fb.isOpen}{#each $fb.strings as s}…{/each}{/if}<button on:click={fb.open}>Rate translations</button>6. Le reste, /core
Section intitulée « 6. Le reste, /core »@sonenta/feedback/core expose le FeedbackClient figé sur lequel tous les adaptateurs sont bâtis : acceptTos(), loadStrings(), rate(), suggest(), transport debouncé/batché, JWT rotatif. Utilisez-le directement pour tout framework sans adaptateur first-party.
// any framework: the frozen client all adapters wrapimport { FeedbackClient } from "@sonenta/feedback/core";
const client = new FeedbackClient({ apiBase: "https://api.sonenta.com", projectId: "proj_xxx", language: "fr",});await client.acceptTos(); // server mints the sessionawait client.loadStrings(); client.rate(/* … */); client.suggest(/* … */);7. Cadrage sur les clés affichées (automatique)
Section intitulée « 7. Cadrage sur les clés affichées (automatique) »Le panneau se cadre automatiquement sur les clés réellement affichées sur la vue courante, via le registre global de clés produit par le SDK @sonenta/*-i18n, aucune configuration. Ne passez un tableau keys explicite qu'en repli (ex. chaînes non issues de @sonenta/*-i18n) ; ne passez jamais tout votre catalogue, cela exposerait toutes les chaînes de l'app, pas celles que l'utilisateur regarde. Le registre est suivi au montage et compté par référence : les chaînes persistantes toujours à l'écran (en-tête, eyebrow) restent enregistrées tant que leur composant est monté, aucun reset par vue. (reset() n'existe que comme échappatoire pour des cas de routage non-React ; le SDK ne l'appelle jamais automatiquement.)
// the panel auto-scopes to keys RENDERED on the current// view, via the global key registry the @sonenta/*-i18n// SDK produces, no config needed:feedbackPlugin({ controllerRef: feedback }); // auto-scoped
// explicit keys = FALLBACK only (e.g. strings not from// @sonenta/*-i18n). NEVER pass your whole catalogue.feedbackPlugin({ keys: ["common:checkout.cta"] });8. Filtre de namespace (optionnel)
Section intitulée « 8. Filtre de namespace (optionnel) »Un écran qui rend plusieurs namespaces peut cadrer le panneau sur celui qui intéresse le client, passez un namespace optionnel (string | string[]) sur le trigger/config (feedbackPlugin() pour React/Native, createFeedback() pour Vue/Svelte, resolveKeys() / filterByNamespace() pour /core). Il se compose après le cadrage sur les clés affichées, affiché = affiché ∩ namespace. Non défini, "" ou [] = aucun filtre (comme avant). Ne retombe jamais sur tout le projet.
// §0d: OPTIONAL namespace filter (customer feature).// Composes AFTER rendered-scoping: shown = rendered ∩ namespace.feedbackPlugin({ controllerRef: feedback, namespace: "quiz" });
// Vue / Svelte, same option on createFeedback:createFeedback({ apiBase, projectId, language, namespace: ["quiz"] });
// /core, resolveKeys / filterByNamespace:resolveKeys(explicit, "quiz"); // or filterByNamespace(keys, "quiz")
// unset / "" / [] ⇒ no filter.9. Session générée côté serveur (tous frameworks)
Section intitulée « 9. Session générée côté serveur (tous frameworks) »La clé de session / regroupement est générée côté serveur au consentement. Le client ne l'envoie ni ne la génère jamais, aucune config groupingKey, aucun champ de requête. À acceptTos() le backend la renvoie (liée au JWT restreint) ; chaque adaptateur l'expose en lecture seule via client.sessionId. Un endUserId de retour conserve sa valeur serveur stable ; un nouvel utilisateur obtient un sess_… neuf.
// session/grouping key is MINTED SERVER-SIDE at consentawait client.acceptTos(); // POST /v1/feedback/tosclient.sessionId; // read-only, e.g. "sess_018f…"
// NO groupingKey config, NO request field:// the client never sends or self-generates the session.10. Consentement et sécurité (automatique)
Section intitulée « 10. Consentement et sécurité (automatique) »Avant la première écriture, l'utilisateur final accepte les CGU utilisateur final Sonenta versionnées. Le SDK obtient ensuite un JWT court limité au scope feedback:write, cryptographiquement séparé de votre auth client, et le renouvelle de façon transparente. Les utilisateurs finaux sont anonymes (id opaque, aucune PII). Vous n'avez rien à coder côté sécurité.
Ce que vous obtenez gratuitement
Section intitulée « Ce que vous obtenez gratuitement »- Zéro re-render hôte. React/Native tournent en feuille sœur isolée du provider i18n ; Vue/Svelte gardent l'état d'ouverture dans leur propre store. Ajouter ou ouvrir le feedback ne re-render jamais votre app.
- Consentement + session serveur gérés. Acceptation des CGU, session générée côté serveur, obtention du JWT, refresh rotatif et un retry transparent unique sur 401, tout dans le SDK. Il ne lève jamais d'exception dans votre rendu.
- Transport debouncé et batché. Notes et suggestions sont mises en file et envoyées sur un debounce (1,5s par défaut), à un batch max, ou à la fermeture. Best-effort : un batch en échec est re-mis en file une fois, puis absorbé.
- Modération avant publication. Rien de soumis par un utilisateur final ne part en ligne automatiquement. Les suggestions arrivent en
pendingdans la file de modération de votre dashboard, vous approuvez, rejetez ou appliquez via le chemin d'édition audité normal. Les notes s'agrègent dans un tableau de bord temps réel par clé / par langue.
- Pourquoi c'est clé: Sonenta vs Lokalise: L'évaluation en contexte par l'utilisateur final est un différenciateur Sonenta.
- Tarifs: Plans et add-ons: L'évaluation par l'utilisateur final est un add-on à partir de Pro.