Aller au contenu

@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.

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.

Fenêtre de terminal
npm i @sonenta/react-i18next @sonenta/feedback
// optional peer deps only for the matching entry: vue · svelte

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 run
import { 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()
OptionTypeDéfaut
controllerRefRef<Controller>-
onReady(c) => void-
keysstring[]auto-discovered
flushDebounceMsnumber1500
maxBatchnumber20
defaultButtonbooleanfalse

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 slot
import { 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 config
import { 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>

@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 stores
import { 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>

@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 wrap
import { 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 session
await client.loadStrings(); client.rate(/* … */); client.suggest(/* … */);

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"] });

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 consent
await client.acceptTos(); // POST /v1/feedback/tos
client.sessionId; // read-only, e.g. "sess_018f…"
// NO groupingKey config, NO request field:
// the client never sends or self-generates the session.

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é.

  • 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 pending dans 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.