// the platform

Everything inside Comvi.

The editor your translators open in your live app. The delivery that skips your release path. The types that catch broken keys at build time. And the rest of what makes a TMS actually work.

Hover. Edit. Done.

Translators install the Comvi Chrome extension, open your live app, and every string becomes editable. They see the actual layout, the actual button width, the actual line break — not a CSV row out of context.

  • hover any string to highlight
  • click to open the editor with all locales side-by-side
  • statuses per locale: not translated needs review translated
  • save once — your production cdn updates within seconds
— how the extension fits —

The extension is for translators, not for your app. Your production code stays clean: no in-context sdk, nothing extra in your bundle, nothing to ship. Translators install it once and use it across every project they work on.

Get the extension → Chrome Web Store COMING SOON
// translator's view
hover Order summary click editor opens
Translation editor
project halberd-app · key checkout.summary.title
4 of 12 locales ▾
translations
context
history
memory
🇬🇧
English
en · base
Order summary
source
🇩🇪
German
de · de-DE
Bestellübersicht
translated
🇫🇷
French
fr · fr-FR
Récapitulatif de la commande
needs review
🇪🇸
Spanish
es · es-ES
— not translated —
not translated
⌘ + S to save · cdn updates within seconds last edited 2m ago by anna@halberd.io

Translations skip your release path.

Translation changes don't go through your build, your CI, or your deploy pipeline. They go through Comvi — saved in the editor, propagated to a global CDN, served to your users.

// flow
editor save
translator clicks save
debounce ~3s
comvi api
batches saves
object storage
r2 bucket
edge cdn
300+ pops
your app
sdk fetches on load
  • cdn-first delivery — translations served from the edge, not your servers
  • debounced auto-deploy — saves are batched and pushed to cdn within seconds
  • global distribution — cloudflare cdn, 300+ regions
  • no infra to manage — no redis, no s3, no edge worker to write
  • your sdk fetches translations on app load
// 300+ pops · cloudflare cdn
p50 latency < 50ms worldwide

Drafts that respect your terminology.

Comvi's AI translation injects your glossary into the prompt — so the draft uses your terms, in the right form, instead of guessing. You pick the engine, you review the result before it ships.

  • choose the engine — openai, anthropic, deepl, google
  • glossary in the prompt for llm providers — handles declension and grammar
  • glossary as editor hint for deepl/google — visible to reviewers
  • translate one key, one locale, or batch the whole project
  • drafts land in needs review — never published without you
openai
gpt-4o · llm
anthropic
claude · llm
deepl
mt · glossary as hint
google
mt · glossary as hint
// prompt construction provider · openai
system:
You are translating for halberd-app. Use the glossary terms exactly as defined. Do not translate brand names.
glossary (injected):
"basket" → "Warenkorb" (de)
"checkout" → "Kasse" (de)
"Halberd" → do not translate
user:
Translate "Proceed to checkout in your basket" to German.
↳ draft "Zur Kasse in Ihrem Warenkorb gehen" needs review

Translations type themselves.

Comvi generates TypeScript types from your translation keys. Autocomplete every key in your IDE. Wrong params, missing tag handlers, typos in keys — all caught at build time, not at runtime in production.

  • autocomplete every key in your IDE
  • typed parameters — wrong types fail to compile
  • typed tag handlers — missing handlers fail to compile
  • framework-aware — react, vue, svelte, solid, next, nuxt
— cli —
$ comvi init # one-time setup, links your project
$ comvi pull # download latest translations + types
$ comvi push # upload extracted keys to comvi
Cart.tsx — vscode
1 import { T } from '@comvi/react'
2
3 <T id="cart.|" />
K cart.empty.title string
K cart.empty.body string
K cart.summary icu · 1 var
K cart.checkout.cta string
K cart.remove.label icu · 1 var

Same string, translated once.

Comvi indexes every translation you've approved and suggests fuzzy matches when something similar shows up again. "Add to cart" and "Add to basket" — 87% match, suggested in one click.

Saves the inconsistency.
Saves the work.

  • suggestions ranked by similarity, with diff highlighting
  • per-project memory, populated as your team translates
  • tmx export
// memory panel 3 fuzzy matches
en→de
Add to basket
cart.add.label · current key
en→de 87%
Zum Warenkorb hinzufügen
"Add to cart" → translated 2 weeks ago
use ↵
en→de 92%
In den Warenkorb
"Add to basket" → translated 5 weeks ago
use ↵
en→de 73%
Zum Korb hinzufügen
"Add to the basket" → translated 11 weeks ago
use ↵

Your terminology, in every translation.

A glossary entry isn't a note for translators to remember — it's a rule. When the AI translates, your glossary lands in the prompt and the model uses your exact term. When DeepL or Google translates, glossary terms highlight in the editor as hints.

// new glossary entry
term
basket
definition
The shopping cart on the checkout page. Always "basket", never "cart".
translations
de
Warenkorb
fr
panier
es
cesta
// editor · german checkout.cta
source · english
Proceed to checkout in your basket.
GLOSSARY HINT
basket → Warenkorb
Always use this exact term.
your translation
Zur Kasse in Ihrem Warenkorb gehen.
  • per-project terminology with definitions and translations
  • two execution modes:
    • with llm provider: glossary injected into prompt
    • with deepl/google: glossary highlighted as editor hint
  • brand names with do not translate flag

Real i18n. Not key-value.

Comvi messages are full ICU — plurals, select for gender and formality, rich-text tags with typed handlers, nested parameters. The same syntax works in @comvi/i18n standalone (MIT-licensed).

app/Cart.tsx
1// "cart.summary":
2// "{count, plural,
3// one {<b>#</b> item from <link>store</link>}
4// other {<b>#</b> items from <link>store</link>}}"
5
6import { T } from '@comvi/react'
7
8<T id="cart.summary" values={{ count: 3 }}
9 components={{
10 b: ({ children }) => <strong>{children}</strong>,
11 link: ({ children }) => <a href="/store">{children}</a>,
12 }}
13/>
1// "greeting.welcome":
2// "{tone, select,
3// formal {Welcome, {name}.}
4// informal {Hey {name}!}
5// other {Hi, {name}.}}"
6
7import { T } from '@comvi/react'
8
9<T id="greeting.welcome"
10 values={{ tone: user.preferences.tone, name: user.name }}
11/>
1// "legal.tos":
2// "By <b>continuing</b>, you agree to our
3// <link>terms</link> and <link2>privacy policy</link2>."
4
5import { T } from '@comvi/react'
6
7<T id="legal.tos" components={{
8 b: ({ children }) => <strong>{children}</strong>,
9 link: ({ children }) => <a href="/terms">{children}</a>,
10 link2:({ children }) => <a href="/privacy">{children}</a>,
11}}/>
1<!-- "cart.summary":
2 "{count, plural,
3 one {<b>#</b> item from <link>store</link>}
4 other {<b>#</b> items from <link>store</link>}}" -->
5
6<script setup lang="ts">
7import { T } from '@comvi/vue'
8</script>
9
10<template>
11 <T id="cart.summary" :values="{ count: 3 }">
12 <template #b="{ children }"><strong>{{ children }}</strong></template>
13 <template #link="{ children }"><a href="/store">{{ children }}</a></template>
14 </T>
15</template>
1<!-- "greeting.welcome":
2 "{tone, select,
3 formal {Welcome, {name}.}
4 informal {Hey {name}!}
5 other {Hi, {name}.}}" -->
6
7<script setup lang="ts">
8import { T } from '@comvi/vue'
9</script>
10
11<template>
12 <T id="greeting.welcome"
13 :values="{ tone: user.tone, name: user.name }"/>
14</template>
1<!-- "legal.tos":
2 "By <b>continuing</b>, you agree to our
3 <link>terms</link> and <link2>privacy policy</link2>." -->
4
5<script setup lang="ts">
6import { T } from '@comvi/vue'
7</script>
8
9<template>
10 <T id="legal.tos">
11 <template #b="{ children }"><strong>{{ children }}</strong></template>
12 <template #link="{ children }"><a href="/terms">{{ children }}</a></template>
13 <template #link2="{ children }"><a href="/privacy">{{ children }}</a></template>
14 </T>
15</template>
@comvi/react · MIT npm i @comvi/react
// frameworks react · vue · svelte · solid · next · nuxt

Plug Comvi into your CI/CD.

Every meaningful event in Comvi can trigger a webhook — your build pipeline, your Slack channel, your custom script. Translations published? Rebuild static pages. Key created? Open a PR with extracted strings.

POST https://your-app.com/webhooks/comvi application/json
1{
2 "event": "translation.published",
3 "project": "acme-store",
4 "key": "cart.summary",
5 "locale": "de",
6 "value": "{count, plural, one {...} other {...}}",
7 "actor": { "id": "u_42", "email": "anna@acme.com" },
8 "timestamp": "2026-04-26T14:21:08Z"
9}
// .github/workflows/rebuild.yml github actions
1name: rebuild-on-translation
2on:
3 repository_dispatch:
4 types: [comvi-translation-published]
5jobs:
6 build:
7 runs-on: ubuntu-latest
8 steps:
9 - uses: actions/checkout@v4
10 - run: npm ci && npm run build
11 - run: npm run deploy
  • hmac-signed payloads — verify with your secret
  • retry with exponential backoff — up to 24h
  • delivery log in dashboard — replay any event
— events you can subscribe to —
translation.publishedtranslation.draft.createdkey.createdkey.deletedproject.locale.addedreview.requested

Roles that match how localization actually works.

Five roles, copied verbatim from the in-product role picker. Per-project membership. Translators can be scoped to specific locales — they only see what they need.

— roles —
owner
full control over organization and all projects
manager
manage organization/project settings and members
editor
create, edit, and delete translation keys
translator
edit translations for assigned languages
viewer
view-only access
  • per-project membership
  • per-locale assignment for translators — work only on de/fr, not the whole language list
// members · halberd-app 6 of 6 · + invite
member role locales
AM
Anna Müller
owner all
JW
Jonas Weiss
manager all
PS
Priya Shah
editor all
LB
Léa Bertrand
translator fr · es
MR
Marco Rossi
translator it
TB
Tom Becker
viewer

Bring what you already have.

Comvi detects the format and preserves your structure. No manual key remapping. Round-trip safe — export, re-import, no drift.

// formats 2 now · 5 soon
JSON
json (i18next)
keys with {{double-brace}} and plural suffixes
available now
JSON
json (flat / nested)
any structure, comvi normalizes
available now
STRI
strings (ios)
.strings · key = value;
soon
XML
strings.xml (android)
resource bundles · plurals tag
soon
ARB
arb (flutter)
application resource bundle
soon
PO /
po / gettext
msgid / msgstr · plural forms
soon
CSV
csv
for spreadsheet workflows
soon
$ comvi import ./locales --format i18next # detect & upload
$ comvi export --locale de --format json > de.json # pull a snapshot
  • no manual key remapping — comvi detects format and preserves structure
  • round-trip safe — export → re-import doesn't drift
// coming from another tool?
More formats land soon. Tell us which one you need: hello@comvi.io
// ready

Now you've seen the platform.

No credit card. Free forever for solo projects.