Turn a client's English site into an indexed Spanish one. How the engine works, why it costs about 2½ cents, and exactly how to install it on a client site.
And the single distinction that separates a real product from a toy.
A client's site is in English. This add-on gives it a Spanish version at /es/* — real URLs, rendered on the server, with hreflang tags — so Google indexes the Spanish pages and ranks them.
The tempting shortcut is a JavaScript widget that swaps visible text when someone clicks a flag. It's easy, it demos well, and it is commercially pointless — Google never sees the Spanish version, so it never ranks, and the client gets zero new customers.
This is why the whole thing lives in Rankee. The buyer isn't paying for a language toggle. They're paying to be findable by people searching in Spanish.
// client-side swap
document.querySelectorAll('p')
.forEach(translate)
Same URL. Nothing in view-source. Googlebot sees English. Zero SEO value.
guarapos.com/menu (en)
guarapos.com/es/menu (es)
<link rel="alternate"
hreflang="es" …>
Real URL, server-rendered, in view-source. Indexed and rankable.
App-boundary ruling #10 — decided 2026-07-14.
Rankee owns website translation as a multilingual-SEO add-on: translated subpath pages (/es/*,/pt/*),hreflangtags, indexed translated content. Rationale: the buyer's motive is search reach — Rankee's job.
The fences that keep the creep zones closed:
| App | May do | May not do |
|---|---|---|
| Rankee | Everything in this doc — the engine, the cache, the dashboard, billing | — |
| Bloggy | Multilingual authoring of its own content; may hand off to Rankee's engine | Translate a whole site |
| Replyee | Answer chat in the visitor's language | Translate the site |
| Compliee / Localey | — | No translation features at all |
Three ideas: hash, batch, cache. The cost model depends entirely on the third.
Source text is normalized (whitespace collapsed, unicode normalized) before hashing. So reformatting a paragraph, changing indentation, or reflowing HTML does not invalidate the cache. Only a genuine wording change triggers a re-translation.
Strings go to Claude 60 at a time, not one at a time. The system prompt is ~600 tokens; sending it once per string instead of once per batch would multiply input cost by roughly 60×. That's the difference between pennies and real money per site.
status: 'reviewed'. A later machine pass can never overwrite a reviewed string. So once you fix a menu item, it stays fixed — which is what makes this safe to point at a restaurant menu at all.
| Piece | Path (in Rankee) |
|---|---|
| Migration | supabase/sql/migration_add_translate.sql |
| Tables | rk_translate_sites · _strings · _values |
| Hashing | src/lib/translate/hash.ts |
| Claude calls | src/lib/translate/translate.ts |
| Read-through cache | src/lib/translate/cache.ts |
| Rate limiting | src/lib/translate/rate-limit.ts |
| API | src/app/api/translate/strings + sites CRUD + values |
| Dashboard | /translate |
| Client-site files | distributable/boom-translate/ |
The margin here is unusual even by SaaS standards.
For a typical 200-string restaurant site, one language:
| Item | Amount |
|---|---|
| Strings | ~200 |
| Average length | ~12 tokens |
| Content tokens | ~2,400 |
| System prompt, amortized over 4 batches | ~2,400 |
| Total input | ~5,000 |
| Total output | ~4,000 |
| Cost @ Haiku 4.5 ($1 / $5 per MTok) | ≈ $0.025 |
Ten client sites in two languages ≈ $0.50 total, forever. Every page render after the first is a Postgres read costing effectively nothing.
Against the ruling's $19–29/mo first language, ~$10/language after, one client at $29/mo returns your entire translation cost for the whole client base in the first hour of the first month.
| Tier | Price | Notes |
|---|---|---|
| First language | $19–29/mo | Per ruling #10 |
| Each additional | ~$10/mo | Marginal cost is ~2.5¢ one time |
| Setup (optional) | $99–199 | Covers your review pass on menu items — the part that actually takes your time |
The honest pitch to a client isn't "we translate your site." It's "we make you findable to Spanish-speaking customers searching in your area." That's what the subpath + hreflang architecture buys, and it's what justifies a monthly rather than a one-off.
Client sites are separate Next.js apps, so this is a copy-in, not an npm install.
bm-vercel, rlg-vercel, haw-portal) are independent Next.js apps deployed separately. There's no monorepo and no private npm registry, so distribution follows the same file-copy pattern already proven by boom-responsive.css and boom-theme.css.
In the Supabase SQL editor at db.boommedia.us, run:
Rankee/supabase/sql/migration_add_translate.sql
Make sure ANTHROPIC_API_KEY is set on Rankee's deployment. Note Rankee is the one app on Coolify, not Vercel — so this is set in Coolify's environment settings, not the Vercel dashboard.
Go to Rankee → Translation. Add the client's site URL, enable Spanish, and copy the generated site key (it starts with rkt_).
From Rankee/distributable/boom-translate/:
boom-translate.config.ts // site key + locales
middleware.ts // detects /es/, rewrites to base route
translate-server.ts // server-side render helper
hreflang.ts // <link rel="alternate"> + sitemap
If the client site already has a middleware.ts, merge rather than overwrite — the README covers this.
BOOM_TRANSLATE_SITE_KEY=rkt_... # from step 3
NEXT_PUBLIC_SITE_ORIGIN=https://client.com # for hreflang URLs
Visit each /es/ page once. The first visit translates and caches; every visit afterward is a database read. Then review the results in the Rankee dashboard and fix any menu items the model got wrong — those edits become permanent.
Two checks. If either fails, the add-on has no commercial value — do not bill for it.
/es/menu, press Ctrl+U, and search for a Spanish phrase. If it's not in the raw HTML, it's being injected client-side, Googlebot will never see it, and the pages will never rank. This is the whole product.
/es/menu's canonical must point at itself. If the Spanish page's canonical points back at the English page, you are explicitly telling Google "don't index this." Both pages should carry reciprocal hreflang tags and self-referencing canonicals.
/es/*/es/menu canonical → /es/menu (not /menu)<link rel="alternate" hreflang="es"> on the English page<link rel="alternate" hreflang="en"> on the Spanish pagehreflang="x-default" present on bothsitemap.xmlFlagged honestly during the build rather than discovered later.
node_modules (typescript/bin/tsc is empty and exits 0 misleadingly). Every file was verified by reading it back, but no typecheck or build has run. Do npm install && npx tsc --noEmit on a local checkout before deploying.
| Limitation | Impact |
|---|---|
| No Stripe billing | Deliberate. TODOs mark the $19–29/mo + ~$10/locale model. Nothing meters or charges yet. |
enabled_locales is unmetered | A user could enable unlimited languages and spend tokens. Fine while you're the only user; must be capped before this is self-serve. |
| Rate limiting is an in-process Map | Effective on Rankee's single Coolify instance. Silently multiplies by instance count if Rankee scales or moves to Vercel. |
?key= auth pattern unverified | Written from the documented Displayee partner-API behavior but not diffed against Displayee's actual code. Worth a look. |
| PostgREST embed syntax | The ownership check in the values route is correct per docs but wants a runtime smoke test. |
| Table prefix inconsistency | Rankee's existing tables are unprefixed (tracked_websites); these use rk_ because the Postgres is shared across apps. Consistent with the shared DB, inconsistent with Rankee itself. Cosmetic. |
| Images and PDFs untouched | A menu embedded in a JPEG stays English. Worth knowing before promising a client "full translation." |
You have four Cuban restaurants in Palm Beach County — Guarapo's, La Casita, Don Ramon Wellington, Don Ramon WPB. They almost certainly have Spanish-speaking customers searching in Spanish and finding nothing.
Pick one. Structured menu content is the easiest thing to translate well, the audience is real, and within a week of Search Console data you'll know whether the quality holds up well enough to sell to the other three.