lingopulse
/docs
Get started →

Overview

Lingo Pulse is an i18n observability dashboard. Like Datadog, but for your translation coverage. Connect a GitHub repository, and Lingo Pulse will scan every locale file on every push, score translation quality via the Lingo.dev SDK, and alert you when a language breaks.

coverage trackingquality scoringwebhook eventsPR checksincident feed

Built on Next.js, Supabase, and the @lingo.dev/_sdk. All open source.

Quick start

1. Clone and install

git clone https://github.com/your-org/lingo-pulse
cd lingo-pulse
npm install

2. Configure environment variables

Copy the example file and fill in your credentials:

cp .env.example .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
LINGO_DEV_API_KEY=your_lingo_dev_key
GITHUB_WEBHOOK_SECRET=any_random_secret

3. Run the database migrations

Create a Supabase project, open the SQL editor, and run all 4 migration files in order:

supabase/migrations/001_initial.sql
supabase/migrations/002_owner_scoping.sql
supabase/migrations/003_pr_checks_repo_pr_unique.sql
supabase/migrations/004_translation_incidents.sql

4. Start the dev server

npm run dev
# → http://localhost:3000

5. Connect a repository

Visit /connect, sign in with GitHub, paste your repository URL, and optionally add a GitHub PAT with admin:repo_hook scope to enable automatic webhook registration.

How analysis works

When a repository is connected (or a webhook push event fires), Lingo Pulse fetches the full file tree via the GitHub API and finds every locale file matching **/*.json or **/*.yaml under common i18n directories.

Coverage score

For each locale, the engine compares keys against the source locale (usually en). Coverage = translated keys ÷ total source keys × 100.

Quality score

Translated strings are passed to the Lingo.dev SDK for quality analysis. The SDK detects placeholder leaks, fallback copy left in source language, empty values, and format mismatches. Each file receives a 0–100 quality score.

Incident detection

A locale is flagged as an incident when its coverage drops below the threshold (default 80%) or its quality score drops more than 10 points between two consecutive analysis runs.

PR Checks

When you sync PRs, Lingo Pulse analyzes any open pull requests that touch locale files. It posts a comment on each PR with:

  • Coverage before and after the changes
  • Number of missing keys introduced or resolved
  • Status: passing, warning, or failing

The comment updates automatically when you re-sync PRs. This helps reviewers catch translation regressions before merging.

Webhooks

Lingo Pulse registers a GitHub webhook on your repository pointing to /api/webhooks/github. Push and pull-request events trigger a fresh analysis run automatically.

Requirements

GitHub PAT scopeadmin:repo_hook. Needed for automatic registration
Public URLWebhooks require a publicly reachable host. Localhost will not work. Deploy to Vercel or use ngrok for local testing.
Webhook secretSet GITHUB_WEBHOOK_SECRET in your env. The handler validates the X-Hub-Signature-256 header on every request.

Local testing with ngrok

npx ngrok http 3000
# copy the https://xxxx.ngrok.io URL
# → update NEXT_PUBLIC_APP_URL in .env.local
# → reconnect the repo to register the webhook

Production Incident SDK

Lingo Pulse includes a browser SDK to catch broken translations in production. Get your credentials from the SDK page in your dashboard.

What it detects

  • Raw keys rendered to users
  • Placeholder leaks like {user_name}
  • Empty translations
  • Fallback-locale renders (when source language shows instead of translated)

Plain HTML / JS

<script src="https://your-deploy-url/lingopulse-browser.js"></script>
<script>
  const pulse = new window.LingoPulse({
    repoId: 'your-repo-id',
    ingestKey: 'your-ingest-key',
    apiBase: 'https://your-deploy-url',
    appVersion: 'web@1.0.0',
  });

  pulse.inspect('checkout.pay_now', {
    locale: 'ja',
    route: '/checkout',
    translationKey: 'checkout.pay_now',
  });
</script>

React / Module

import { LingoPulse } from '@/lib/sdk/lingopulse';

const pulse = new LingoPulse({
  repoId: 'your-repo-id',
  ingestKey: 'your-ingest-key',
  apiBase: 'https://your-deploy-url',
  appVersion: 'web@1.0.0',
});

Wrap your translator

const t = pulse.wrapTranslator(i18n.t.bind(i18n), (key) => ({
  locale: i18n.language,
  route: window.location.pathname,
  translationKey: key,
}))

Direct inspect

const label = pulse.inspect(i18n.t('checkout.pay_now'), {
  locale: i18n.language,
  route: window.location.pathname,
  translationKey: 'checkout.pay_now',
});

Raw HTTP payload

Send incidents directly over HTTP without the SDK:

POST https://your-deploy-url/api/incidents/report
{
  "repoId": "your-repo-id",
  "ingestKey": "your-ingest-key",
  "issueType": "fallback",
  "locale": "ja",
  "route": "/checkout",
  "translationKey": "checkout.pay_now",
  "sampleText": "Pay now",
  "appVersion": "web@1.0.0"
}

Credentials

Get your credentials from the SDK page in your dashboard:

  • repoId - identifies your project
  • ingestKey - public key for incident ingestion
  • apiBase - your Lingo Pulse deployment URL

Set a distinct appVersion per app or surface (e.g., web@1.4.2, marketing@2026-03-13) so incident feeds show which SDK source reported each issue.

API reference

POST
/api/repos

Connect a new repository. Body: { repoUrl, githubPat?, lingoApiKey? }

GET
/api/repos

List all repos for the authenticated user.

GET
/api/repos/[id]

Fetch full dashboard data for a repo (locales, coverage, quality, activity).

POST
/api/analyze

Manually trigger a re-analysis run. Body: { repoId }

POST
/api/webhooks/github

GitHub webhook receiver. Validates X-Hub-Signature-256 and triggers analysis.