Aller au contenu
glot
Menu

Labs (expérimental)

Segmented experiments

Test several versions of the same message (a hero, a call to action) in every language at once, for the audiences you choose, and keep the one that converts. The variant is chosen at the edge and rendered by the server: visitors never see the official text flash and then change. Requires the self-hosted server or glot cloud, and the Insights beacon for conversions.

Experimental. The API under /api/projects/{slug}/xp may still change. The older one-key, one-locale copy experiments keep working unchanged.

Concepts

  • Experiment: a group of keys tested together (for example home.hero.title, home.hero.subtitle, home.hero.cta), in all the locales you pick. Testing the title without its button would mix messages; a group keeps them consistent.
  • Arm (intention): one idea expressed in every locale, such as social proof or direct benefit. Arm 0 is always your official catalog. The other arms are written by you or generated by glot from an intention, then validated like any translation (placeholders, glossary, length budget).
  • Segment: a named rule on the visitor context, for example country is DE, AT or CH. An experiment targets up to 4 segments, in priority order; everyone else falls into the * cell. Each cell learns on its own.
  • Goal: the conversion event that decides (signup, purchase with a revenue value), plus optional guardrail events that must not go up (refund).
  • Holdout (optional): a small share of visitors who never see any variant, to measure the combined effect of everything you promoted.

Any site (one script tag)

No React, no bundler (static HTML, Leptos/WASM, Rails, …). Conversions are counted by the Insights browser build, dist/insights.global.js from @glot-i18n/insights (about 1.1 kB gzipped), served by your app. It configures itself from its own tag:

<script src="/insights.global.js" data-key="PUBLIC_KEY" data-endpoint="https://glot.example.com" data-locale="fr" defer></script>
  • data-key (required): the project's Insights public key. Without it the script does nothing.
  • data-endpoint: your glot server origin (default: same origin as the page).
  • data-locale: the locale the page is served in. Omitted, it is read from <html lang>, else en.

Send a conversion from plain JS, wherever the visitor converts:

window.glot.track("signup");                                  // or window.glot.track("purchase", { value: 49, currency: "EUR" })

The arm itself is chosen at the edge, not by this script: put the Cloudflare Worker template (@glot-i18n/edge/worker, with GLOT_KEY) in front of the site, mark the tested text with data-glot="key", and use persistence: "session" so the glot_xp cookie lets track credit the arm the visitor saw. A page with no assignment still counts the event, just not for an experiment.

Install (3 lines)

With Next.js (App Router) and next-intl:

// proxy.ts — the edge decides the arm, with no blocking call to glot
export const proxy = glotProxy({ source: "en", locales: ["fr", "de"], experiments: { server: "https://glot.example.com", project: "acme", key: "PUBLIC_KEY" } });
// i18n/request.ts — the server renders the chosen arm
return { locale, messages: withExperiments(messages, { header: (await headers()).get("x-glot-xp"), locale }) };
// where the visitor converts
track("signup");                                     // or track("purchase", { value: 49, currency: "EUR" })

glotProxy comes from @glot-i18n/edge/next, withExperiments from @glot-i18n/runtime/experiments, track from @glot-i18n/insights (keep its init in your layout). PUBLIC_KEY is the project's Insights key. Your t() calls do not change. To credit conversions on later pages without a cookie, also wrap your layout in <GlotExperiments header={(await headers()).get("x-glot-xp")}> (@glot-i18n/runtime/next-intl), inside NextIntlClientProvider. A complete app lives in examples/experiments-demo.

How it works: the proxy reads a small public file, GET /cdn/{project}/experiments.json (cached, refreshed every minute), picks an arm locally and passes it to your app in a request header. The server renders the HTML with that arm, so the hydrated page is identical. Exposures are reported in the background. If glot is unreachable or the file is older than 24 hours, everyone gets the official text and nothing is counted: glot is never on your critical path.

Pages under test vary per visitor: the proxy marks them Cache-Control: private so a shared cache never stores one visitor's arm for everyone.

Static sites: the Cloudflare Worker template applies the same decision to data-glot elements with HTMLRewriter. Single-page apps without a server render can keep the client-only path (useGlotVariant), which may flicker.

Create an experiment

In the dashboard, Experiments → New: pick the keys (or click them in the preview), the audience, the variants and the goal. Or through the API:

curl -X POST "$GLOT/api/projects/acme/xp" -H "Authorization: Bearer $GLOT_TOKEN" -H "content-type: application/json" -d '{
  "name": "Hero by country",
  "prefix": "home.hero",
  "locales": ["en", "fr", "de"],
  "cells": [{"name": "Germany", "rule": {"all": [{"attr": "country", "in": ["DE"]}]}},
            {"name": "France",  "rule": {"all": [{"attr": "country", "in": ["FR"]}]}}],
  "allocation": "segmented",
  "goal": "signup",
  "arms": [{"intent": "social proof", "values": {"en": {"home.hero.title": "Join 2,000 teams who get paid on time"}, "…": {}}}]
}'
curl -X POST "$GLOT/api/projects/acme/xp/$ID/start" -H "Authorization: Bearer $GLOT_TOKEN"

start refuses (422 not_ready) while an approved arm has a missing or invalid text in one of the locales; GET …/xp/{id}/check lists what is wrong. Preview any arm with ?glot_xp=<experiment id>.<arm> (never counted).

Segments

A segment is a list of conditions that must all be true:

{"all": [{"attr": "country", "in": ["DE", "AT", "CH"]}, {"attr": "source", "eq": "search"}]}
Attribute Where it comes from Values
country, region your CDN's geo headers (cf-ipcountry, x-vercel-ip-country…) ISO codes
locale the locale being served your locales
device client hints, else the user agent mobile, tablet, desktop
source utm_source / referrer search, social, email, paid, direct, referral
visitor only with consent (consent option) new, returning, unknown
plan, logged_in, custom.* your app: experiments: { attrs: (req) => ({ plan: "pro" }) } short declared values

Operators: eq, in, not_in. Segments are evaluated at the edge; only the name of the matched cell reaches glot, never the raw attributes.

Keep segments few and large. Each one needs its own traffic: the dashboard estimates the duration while you build the rule, and suggests merging a segment that would need more than 90 days.

Statistics, simply

  • glot counts, per cell and arm, how many visitors saw it and how many converted. Nothing else.
  • From those counts it computes, for each arm, how likely it is to be the best and how much better than the official text it probably is, with a range: "Variant B: +12 % signups (probably between +4 % and +20 %), confidence 97 %".
  • Optimize automatically (default) gradually sends more traffic to the arms that look better, while still testing the others (at least 10 % each). Fixed split keeps the split you set; choose it when you need equal, constant traffic per arm.
  • A winner is declared only after every arm has enough visitors (500 by default) and at least 7 days, so a busy Monday does not decide. glot looks at the data a few times (each time traffic doubles), not after every visit, and the bar is raised for each look, arm and segment so that running many comparisons does not create fake winners.
  • Winner for one segment only is claimed when the segment has its own clear winner and that arm does clearly better there than overall. Otherwise glot recommends promoting everywhere.
  • Sample ratio mismatch: if the traffic split does not match what was planned (a bug, a cache, a bot), a red banner appears and decisions freeze until you look.
  • Breakdowns by country, device or source are shown as exploratory: interesting, never a basis for a decision.

Promote

  • Promote everywhere writes the winning texts into your catalogs for every locale (reviewed, with history, delivered by the CDN or a pull request). The experiment ends.
  • Promote for this segment keeps serving the winner to that segment only (personalization), without counting. glot reminds you to re-test after 90 days and turns it off if the source text changes.

Good practices

  • Test one idea per arm, and keep arms meaningfully different; three arms plus the official text is plenty.
  • Start with the audience you have: one global experiment beats four starved segments.
  • Do not test prices, legal text or promises you cannot keep. Keys tagged pricing or legal are refused, and generated variants may only cite the facts you declared.
  • Let it run full weeks. Do not stop at the first good-looking day.
  • Use persistence: "session" for multi-page funnels so a visitor keeps the same arm from the landing page to signup.
  • Keep variants equivalent in meaning for search engines (see below).

FAQ

Does it slow my pages down? No blocking call: the decision uses a cached file and runs in microseconds; exposures are sent after the response. The variant is in the first HTML, so there is no layout shift.

What if glot is down? Visitors get your official catalog, and nothing is counted. The cached configuration keeps working for up to 24 hours.

What do search engines see? Exactly what a human sees: glot never chooses based on the user agent, because serving crawlers a different version is cloaking. A crawler gets a random arm like anyone else, on the same URL with the same canonical. Bot visits are simply not counted.

Does it set cookies? Not by default (persistence: "none"): the arm is kept in sessionStorage after the first page. With persistence: "session", one first-party session cookie glot_xp holds only experiment.arm.cell, no identifier. With consent returning true, a glot_seen=1 cookie (1 year) tells new from returning visitors. See the privacy notes in the repository (docs/legal/experiments-privacy.md) and ask your DPO.

Can visitors opt out? Global Privacy Control (Sec-GPC: 1) and Do Not Track mean: official text, nothing counted, no cookie. To wait for consent, pass the experiments option to glotProxy only once your consent tool allows it (or return false from consent to avoid the returning-visitor cookie).

How long does a test take? It depends on your traffic and conversion rate. The dashboard estimates it when you create the experiment ("≈ 19 days to detect +20 %").

Can I run several experiments? Yes, on different keys. A key belongs to one live experiment at a time.

Modifier cette page sur GitHub