Skip to content
Verbumia

SDK · End-user evaluation

Preview

@verbumia/feedback

Let your own end users rate (5★) and suggest translations from inside your shipped app. One package, five entry points — /react, /native, /vue, /svelte, /core — same wire, same server-minted session, same moderation back office. React & React Native plug into the @verbumia/*-i18n provider you already run (no second context, no host re-render); Vue & Svelte are idiomatic standalone adapters. Available as a Pro+ paid add-on.

The @verbumia/feedback package ships with the End-user translation evaluation add-on at the Verbumia V1 launch. It is not on npm yet — the wire contract is frozen (v3); the framework bindings are stabilising and may still change before launch.

1. Install (at launch)

One package. Import the entry for your framework: @verbumia/feedback/react, /native (RN/Expo), /vue, /svelte, or /core for anything else. vue / svelte are optional peer deps — only the matching entry needs them. Published with the add-on at the V1 launch.

terminal
1// ships with the End-user evaluation add-on at the V1 launch2npm i @verbumia/react-i18next@^0.7.0 @verbumia/feedback@^0.2.03// feedback peerDep: @verbumia/react-i18next >= 0.7.04// optional peer deps only for the matching entry: vue · svelte

2. React (web) — i18n plugin

Add feedbackPlugin() to your existing @verbumia/react-i18next (>= 0.7.0) provider's plugins slot — no new provider, no second context. The provider calls the plugin's setup() once and reuses its own apiBase / projectId / defaultLocale. The panel mounts as an isolated sibling leaf with a private open/close store, so opening it never re-renders your host tree. Trigger from your own CTA via the controller delivered through controllerRef (or the onReady callback).

main.tsx
1// src/main.tsx — plugin of the i18n provider you already run2import { VerbumiaProvider } from "@verbumia/react-i18next"; // >= 0.7.03import { feedbackPlugin } from "@verbumia/feedback/react";4import { useRef } from "react"; 6const feedback = useRef(null); 8<VerbumiaProvider9  projectId="proj_xxx"10  apiKey={import.meta.env.VITE_VERBUMIA_KEY}11  plugins={[ feedbackPlugin({ controllerRef: feedback }) ]}12>13  <App />14</VerbumiaProvider> 16// own CTA — does NOT re-render the host tree17<button onClick={() => feedback.current?.open()}>Rate translations</button>
All feedbackPlugin() / createFeedback() options
Option Type Default
controllerRefRef<Controller>
onReady(c) => void
keysstring[]auto-discovered
flushDebounceMsnumber1500
maxBatchnumber20
defaultButtonbooleanfalse

3. React Native / Expo

Identical pattern from the /native entry: add feedbackPlugin() to the same @verbumia/react-i18next provider's plugins slot in your Expo app and trigger via the controller. No extra native modules; token storage uses the platform secure store.

App.tsx
1// App.tsx (Expo / React Native) — same plugins slot2import { VerbumiaProvider } from "@verbumia/react-i18next";3import { feedbackPlugin } from "@verbumia/feedback/native"; 5<VerbumiaProvider6  projectId="proj_xxx"7  apiKey={process.env.EXPO_PUBLIC_VERBUMIA_KEY}8  plugins={[ feedbackPlugin({ onReady: (c) => (ctrl = c) }) ]}9>{/* … */}</VerbumiaProvider> 11// wire ctrl.open() to your own button / FAB

4. Vue

@verbumia/feedback/vue is a standalone adapter — explicit config, no i18n provider to inherit from. createFeedback(config) returns { client, isOpen, controller, FeedbackPanel }. Mount <FeedbackPanel /> once near your root (it Teleports to body), and call controller.open() from your own CTA. Same isolated open-state — it never re-renders your app.

App.vue
1// main.ts — standalone adapter, explicit config2import { createFeedback } from "@verbumia/feedback/vue"; 4export const { controller, FeedbackPanel } = createFeedback({5  apiBase: "https://api.verbumia.dev",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

@verbumia/feedback/svelte is headless and idiomatic: createFeedback(config) returns Svelte stores — isOpen (Writable), strings (Writable) — plus open(), close(), loadStrings(), rate(), suggest(). You render your own panel from the stores; the SDK owns transport, consent and the server session.

+page.svelte
1// feedback.ts — headless idiomatic stores2import { createFeedback } from "@verbumia/feedback/svelte"; 4export const fb = createFeedback({5  apiBase: "https://api.verbumia.dev",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. Anything else — /core

@verbumia/feedback/core exposes the frozen FeedbackClient all adapters are built on: acceptTos(), loadStrings(), rate(), suggest(), debounced batched transport, rotating JWT. Use it directly for any framework without a first-party adapter.

feedback.ts
1// any framework — the frozen client all adapters wrap2import { FeedbackClient } from "@verbumia/feedback/core"; 4const client = new FeedbackClient({5  apiBase: "https://api.verbumia.dev",6  projectId: "proj_xxx", language: "fr",7});8await client.acceptTos();   // server mints the session9await client.loadStrings(); client.rate(/* … */); client.suggest(/* … */);

7. Rendered-key scoping (automatic)

The panel auto-scopes to the keys actually rendered on the current view, via the global key registry the @verbumia/*-i18n SDK produces — no configuration. Pass an explicit keys array only as a fallback (e.g. strings not sourced from @verbumia/*-i18n); never pass your whole catalogue — that would surface every string in the app, not the ones the user is looking at. The registry is mount-tracked and ref-counted: persistent always-on-screen strings (a header, an eyebrow) stay registered while their component is mounted — there is no per-view reset. (reset() exists only as an escape hatch for non-React routing edge cases; the SDK never calls it automatically.)

scoping.ts
1// the panel auto-scopes to keys RENDERED on the current2// view, via the global key registry the @verbumia/*-i18n3// SDK produces — no config needed:4feedbackPlugin({ controllerRef: feedback });   // auto-scoped 6// explicit keys = FALLBACK only (e.g. strings not from7// @verbumia/*-i18n). NEVER pass your whole catalogue.8feedbackPlugin({ keys: ["common:checkout.cta"] });

8. Namespace filter (optional)

A screen that renders several namespaces can scope the panel to just the one the customer cares about — pass an optional namespace (string | string[]) on the trigger/config (feedbackPlugin() for React/Native, createFeedback() for Vue/Svelte, resolveKeys() / filterByNamespace() for /core). It composes after rendered-scoping — shown = rendered ∩ namespace. Unset, "", or [] means no filter (identical to before). It never falls back to the whole project.

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. Server-minted session (all frameworks)

The session / grouping key is minted server-side at consent. The client never sends or self-generates it — there is no groupingKey config and no request field. On acceptTos() the backend returns it (bound into the scoped JWT); every adapter exposes it read-only as client.sessionId. A returning endUserId keeps its stable server value; a new end user gets a fresh sess_….

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. Consent & security (automatic)

Before the first write, the end user accepts the versioned Verbumia end-user ToS. The SDK then acquires a short-lived JWT scoped to feedback:write only — cryptographically separate from your customer auth — and rotates it transparently. End users are anonymous (opaque id, no PII). You ship nothing security-side.

What you get for free

Next