# 08 — i18n Integration: Localization Platforms × the Soapbox Stack

*Where a modded, bilingual TresPies fork would actually plug in — verified against the real Ditto, Armada, Agora, and Shakespeare/MKStack repos, not just their marketing pages.*

The tool landscape and stack-readiness research below (§1, §2, §4, and the comparison table in §5) apply to any team evaluating this stack for localization. §3 and the adoption runbook in §5 are the maintainers' own integration plan for their bilingual fork — a worked example of wiring a parity gate into a real firm; adapt the shape, swap the specifics for your own.

**Verified: 2026-07-09**

Confidence: High for stack-readiness facts (all four products' actual GitLab repos fetched — `package.json`, locale files, router code, `AGENTS.md` — not inferred from docs). High for the Nostr language-tagging verdict (both proposal PRs fetched directly, states confirmed). Medium for the landscape/tool comparison (aggregated from current search results, not independent primary-source fetches of every vendor). Medium-Low for cost figures, which move fast and weren't independently re-verified per vendor.

---

## 1. The 2026 Landscape — Legacy TMS vs. AI/Agent-Native

The field has split into two generations: TMSes built for a human-translator queue, and a newer layer built to sit inside a git repo and a CI pipeline with an LLM doing the first pass.

| Tool | Category | License | Agent/CI-native? | What's distinctive |
|---|---|---|---|---|
| **inlang / Paraglide-JS** | Compiler-based i18n library + ecosystem (Fink editor, Sherlock VS Code extension) | MIT [1] | Yes — git-native message files, Sherlock gives inline IDE translation, Fink lets a translator edit without cloning | Compiles to tree-shaken per-message functions; up to 70% smaller i18n bundles than runtime libraries [1] |
| **Lingo.dev** | "Localization engineering platform" — compiler + CLI + CI Action | Apache-2.0 [2] | Yes, explicitly — GitHub Actions/GitLab CI/Bitbucket Pipelines trigger translation on every push, PRs come back with localized strings [2] | Stateful "localization engines": glossary + brand-voice rules + per-locale model chains + AI quality scoring, configured once [2] |
| **Tolgee** | Self-hostable TMS, developer-first | Apache-2.0 core; Enterprise features dual-licensed (`ee/` dir) [3] | Partial — strong git/CI integration and in-context editing, but built around a hosted/self-hosted web app, not a pure CLI | In-context editing (click text on the live page to translate it); native React/Vue/Next SDKs [4] |
| **Weblate** | Self-hostable TMS, translator-first | GPL-3.0 | Partial — mature git integration (its core design), but workflow center-of-gravity is a translator UI, not agent-driven | Oldest and most battle-tested OSS TMS (2012); the safe, boring choice [4] |
| **Crowdin / Lokalise / Phrase** | Incumbent SaaS TMS | Proprietary | Increasingly — Crowdin now brands "AI-powered localization," 700+ integrations | Deepest integration catalogs (Figma, Slack, Jira); enterprise support; no self-host on the free tiers [4] |
| **i18next / react-i18next** | Runtime i18n library | MIT | Neutral — it's a library, not a pipeline; agent-nativeness depends on what you wire around it | Largest ecosystem, browser + Node, non-ICU key format; **already running in this exact stack** (§2) |
| **FormatJS / react-intl** | Runtime i18n library, ICU-strict | BSD-3 | Neutral | Built on the browser's native `Intl` API; best when translators already work in ICU MessageFormat [5] |
| **LinguiJS** | Compiler-based i18n library | MIT | Neutral, but compiler step fits a CI pipeline cleanly | Smallest bundle (~3KB core vs. i18next's ~8-20KB) [5]; PO-file extraction, co-located messages |

**What's genuinely cutting-edge mid-2026:** the AI-MT layer, not the runtime library. REPORTED across multiple vendor blogs: the current pattern is glossary + brand-voice-trained LLM + "quality-based routing" that scores each translated segment and only escalates the weak ones to a human editor, rather than a human reviewing every string [6]. Lingo.dev and Crowdin both ship this; inlang's ecosystem (Fink/Sherlock) is compiler-and-editor-native rather than AI-MT-native — it assumes a human or an external MT step fills the message files.

Confidence: Medium — landscape claims are aggregated from current comparison articles and vendor pages, not independently fetched primary text for every row.

---

## 2. Stack i18n Readiness — Repo-Verified

Fetched each product's actual GitLab tree, `package.json`, and (where present) locale files and routing code. This is the load-bearing section — no marketing-page claims below.

| Product | Repo | i18n library in `package.json` | Locales shipped | Spanish? | Evidence |
|---|---|---|---|---|---|
| **Ditto** | `soapbox-pub/ditto` | *none* | *none* | No | No `i18n`/`intl`/`locale` dependency among 155 deps; `src/` has no locale directory [7] |
| **Armada** | `soapbox-pub/armada` | *none* | *none* | No | Same check, 99 deps, zero hits; repo genuinely skeletal (`concord-v1`/`concord-v2` protocol dirs, no locale dir) [8] |
| **Agora** | `soapbox-pub/agora` | `i18next` 26.0.5, `react-i18next` 17.0.4, `i18next-browser-languagedetector` 8.2.1 | **16**: en, es, ar, fa, fr, hi, id, km, ps, pt, ru, sn, sw, tr, zh, zh-Hant | **Yes — 2,401 of 2,402 keys (99.96%)** | `src/i18n.ts`, `src/locales/*.json` fetched directly; keys diffed programmatically [9] |
| **Shakespeare** (builder UI) | `soapbox-pub/shakespeare` | `i18next` 25.5.2, `react-i18next` 15.7.3 | **6**: en, ha (Hausa), ig (Igbo), pt, yo (Yoruba), zh | **No** | `src/locales/` listed directly; 574 leaf keys in `en.json` [10] |
| **MKStack template / scaffold** | inferred from `soapbox-pub/soapbox.pub` (package name is literally `"mkstack"`) + `mkstack-nsp`'s `NIP.md` spec | *none* | *none* | No | Zero i18n deps in the marketing site's `package.json`; the 1,085-line "Nostr SPA Builder" spec that governs what gets scaffolded contains **zero** mentions of language/locale/i18n [11][12] |

```mermaid
flowchart LR
    A[Ditto: zero i18n] -.same family.-> C
    B[Armada: zero i18n, brand new] -.same family.-> C
    C["Agora: full i18next stack\n16 locales incl. es (99.96% parity)"]
    D["Shakespeare builder UI:\ni18next, 6 locales, NO es"]
    E["MKStack scaffold/NIP.md:\nno i18n baked in — prompt-driven"]
    C -->|"proven, copyable pattern"| F[Recommended baseline for a TresPies fork]
```

**Agora is the reference implementation, and it's better than a generic tutorial pattern.** Its `src/i18n.ts` bundles only English eagerly (sync, no flash of untranslated content) and lazy-loads every other locale as its own Vite chunk on selection or detection — REPORTED as saving roughly 2MB off the initial bundle, DOCUMENTED directly in the code's own comments:

> "Only English is bundled eagerly. Every other locale is loaded on demand via a dynamic import()" [9]

It also handles RTL languages (`ar`, `fa`, `ps`, +4 more) by flipping `document.dir` and relying on Tailwind's `rtl:` variant, and resolves region/script variants (`pt-BR`→`pt`, `zh-TW`/`zh-HK`→`zh-Hant`) to the right JSON chunk.

**Agora already runs a parity gate — looser than the maintainers' own TresPies standard.** Its `AGENTS.md` (written for AI coding agents working in the repo) states:

> "`src/test/locales.test.ts` fails the build if any locale ships a key that doesn't exist in `en.json`, but the inverse (a key missing from a non-English locale) is allowed and falls back to English at runtime" [13]

That's an **asymmetric** gate (blocks drift one direction, tolerates it the other) — looser than the maintainers' existing same-commit EN+ES parity rule. The same file also instructs agents to work the way the maintainers' own workspace already works:

> "dispatch the per-language edits to subagents in parallel rather than translating fifteen files sequentially" [13]

Confidence: High — every figure above came from a direct fetch of the file in question, not a changelog or blog post.

---

## 3. Integration Architecture for a Modded TresPies Fork

```mermaid
flowchart TD
    SRC["en.json — source of truth\n(copy Agora's src/i18n.ts pattern\ninto Ditto/Armada fork)"] --> EXT[String extraction: i18next-scanner or manual key discipline]
    EXT --> MT["Lingo.dev CLI/CI\nAI-MT with TresPies glossary\n+ brand-voice rules"]
    MT --> REV["Native-Spanish human review\n(the maintainers' existing culture — PR-based)"]
    REV --> GATE["CI parity gate\nlocales.test.ts, made SYMMETRIC:\nfail on missing OR extra keys"]
    GATE --> BUILD[Vite static build]
    BUILD --> HOST1[Conventional static host\nCloudflare Pages / Vercel]
    BUILD --> HOST2["nsite (Blossom + relay)\noptional censorship-resistant mirror"]
    HOST1 --> DETECT["Client-side detection\ni18next-browser-languagedetector\nlocalStorage -> navigator -> htmlTag"]
    HOST2 --> DETECT
```

**Locale routing on static hosting — three options, one clear default:**

| Pattern | How it works | Static-host fit | Verdict for this stack |
|---|---|---|---|
| **Client-side detection, no URL segmentation** | Browser `Accept-Language` + `localStorage` pick the locale; one bundle, one URL for every language | Perfect — this is what Agora ships today [9][14] | **Recommended default.** Zero extra deploy complexity, proven in-family |
| **Path-prefix routing** (`/es/...`) | React Router locale segment; still one SPA bundle | Works if the host does SPA-fallback (serves `index.html` for unmatched paths) — true of Cloudflare Pages, Vercel, Netlify, and nsite/Blossom gateway resolution alike | Only worth it for SEO — a static Nostr SPA behind client-rendered content gets little SEO value from this today |
| **Separate nsite deployment per locale** | A named nsite (kind 35128, `d` tag = locale code) resolves to its own subdomain, independent of the root site (kind 15128 = npub-only) [15] | Fully supported — this is exactly what Agora's own `nsiteSubdomain.ts` implements for named sites in general [15] | Reserve for a fully independent censorship-resistant Spanish-language mirror, not routine bilingual support |

**Typography/formatting for `es`:** use the web-standard `Intl` API rather than hand-rolled formatting — `Intl.DateTimeFormat('es', …)`, `Intl.NumberFormat('es', …)` (decimal comma, currency placement), and `Intl.PluralRules('es')` for the two-form (singular/plural) Spanish plural system, simpler than languages Agora already handles (Arabic has six plural forms). i18next's ICU/plural plugin and FormatJS both wrap this natively; nothing custom is needed. *(Confidence: High — this is stable web-platform behavior, not a Soapbox-specific claim.)*

**On Shakespeare specifically — can you prompt it to generate a bilingual site?** The MKStack scaffold has no i18n opinion baked in (§2), so yes, but the AI has to be told the pattern explicitly rather than reaching for a convention. INFERRED, practical recommendation: paste Agora's `src/i18n.ts` as a reference into the Shakespeare chat and ask it to replicate the lazy-load + detection pattern with `en`+`es` locale files — this reuses a pattern proven inside the same company's own product family rather than inventing one per generated site.

Confidence: Medium — the pipeline and routing recommendations synthesize DOCUMENTED repo evidence with INFERRED architectural judgment; flagged inline above.

---

## 4. Nostr-Layer i18n Gaps — No Ratified Standard

**Verdict: there is no ratified NIP for natural-language content tagging.** Two proposals exist; neither merged.

| Proposal | Author | Status | What it would have added |
|---|---|---|---|
| **NIP-37 "Language Tag"** | Alex Gleason — the Soapbox/Ditto founder himself [16] | **Closed, unmerged**, 2024-06-07 | A dedicated `lang` tag on text events |
| **NIP-XXX "Internationalization and Localization"** (PR #1127) | eznix86 | **Still open**, last activity 2026-03-04, unmerged | `l`-tag language marking plus a `kind:0` (profile) language field |
| **NIP-32 Labeling** (ratified, general-purpose) | — | **Ratified, in production use** | The de facto working pattern: `["L","ISO-639-1"], ["l","en","ISO-639-1"]` — a spec example states plainly, "Author is labeling their note language as English using ISO-639-1" [17] |
| **NIP-50 Search** | — | Ratified | Search-time filter only: `language:<two-letter ISO 639-1 code>` [18] |
| **NIP-C0 Code Snippets** | — | Ratified | Also uses an `l` tag — but for *programming* language ("javascript", "python"), a different namespace. Don't confuse the two `l` tags across NIPs [19] |

Even Soapbox's own founder couldn't get a dedicated language tag ratified. The reason it stalled, DOCUMENTED verbatim from the PR thread:

> "I think users can't be trusted to specify this tag correctly and all automatic translation tools will already detect the language automatically anyway." [16]

**Practical patterns for bilingual posting, given no standard:** (a) **dual posts** — publish separate EN and ES events, each self-labeled via NIP-32's `l`/`L` tags, letting language-aware clients filter; (b) **inline-both** — one event containing both languages in the body, which sidesteps tagging entirely but degrades UX and can't be filtered; (c) **per-language accounts** — separate npubs per language, cleanest for filtering but fragments the identity (see the org-account tension already documented in chapter 06 [20]). **What clients actually render:** most Nostr clients show the raw event content regardless of any `l` tag present — language tags are a filtering/search signal, not a rendering instruction, so a bilingual post still displays as one undifferentiated block unless the client explicitly builds a language switcher around it. No evidence found of any major client (Ditto included) doing that today.

Confidence: High on the ratification status (both PRs fetched directly, states unambiguous); Medium on "what clients actually render," which is a general-knowledge claim not independently verified against every client's source in this session.

---

## 5. Recommendation

| Platform | License | OSS/AGPL fork fit | Agent/CI automatable | EN↔ES quality + glossary | Static-host fit | Cost @ small team |
|---|---|---|---|---|---|---|
| **i18next + react-i18next** | MIT | Excellent — already MIT, already living inside an AGPL-3.0 app (Agora) [9] | Neutral (library, not a pipeline) | Depends entirely on what feeds it | Excellent — proven in this exact stack | Free (library only) |
| **Lingo.dev** | Apache-2.0 | Excellent — Apache-2.0 is GPLv3/AGPLv3-compatible [2] | **Best-in-class** — CLI + CI Action is the point [2] | Strong — glossary + brand-voice + quality scoring built in [2] | Compiler targets Vite/Next; framework-agnostic CLI works with any static build | Usage-based; free/OSS tier for the CLI itself |
| **inlang / Paraglide-JS** | MIT | Excellent | Good — Sherlock/Fink are git-native, but no AI-MT layer of its own | None built-in — bring your own MT | Excellent — compiles to a static bundle by design | Free (self-hosted message files) |
| **Tolgee** | Apache-2.0 core | Good — core is Apache-2.0; EE features are dual-licensed [3] | Good — strong git/CI hooks, but centered on a hosted app | Good — in-context editing aids human review | Good | Free self-host tier (≤10 seats) [3] |
| **Weblate** | GPL-3.0 | Good — copyleft-native, philosophically closest to AGPL | Fair — mature git integration, translator-UI center of gravity | Good, human-review-first | Good | Free (self-hosted) |
| **Crowdin** | Proprietary | Poor — can't live inside a published AGPL fork | Good — many integrations | Strong — mature AI-MT + QA | Good | No self-host on entry tiers |
| **LinguiJS** | MIT | Excellent | Good — compiler step fits CI | None built-in | Excellent — smallest bundle | Free |

**Ranked recommendation:** don't replace the runtime — extend it. **i18next/react-i18next stays** (it's already MIT, already proven inside an AGPL-3.0 Soapbox product, and copying Agora's `src/i18n.ts` into Ditto/Armada is near-zero migration cost). **Add Lingo.dev** as the AI-MT + CI automation layer on top — it's the one tool on this list built specifically to sit in a CI pipeline with a glossary and brand-voice config, which is exactly the "cutting-edge" piece the current stack is missing. **Skip a new TMS platform for now** — tightening Agora's existing (asymmetric) `locales.test.ts` into a symmetric same-commit gate gets the maintainers' existing discipline without adopting Tolgee/Weblate's operational overhead; revisit if the team scales past ad hoc PR review.

**5-step adoption runbook:**
1. Fork Agora's `src/i18n.ts` + `src/locales/en.json` pattern into the Ditto/Armada-derived TresPies base — proven, in-family, zero new dependencies to evaluate.
2. Stand up Lingo.dev: one `i18n.json` config pointing at `src/locales/en.json`, TresPies glossary + brand-voice rules configured once, CI Action wired to GitHub/GitLab so every push opens a translated-string PR.
3. Tighten the parity gate — extend the `locales.test.ts` pattern to fail the build on missing keys too, not just extra ones, matching the existing TresPies same-commit EN+ES rule.
4. Route every AI-MT'd PR through native-Spanish human review before merge — the existing TresPies review culture, unchanged; Lingo.dev's human-in-the-loop review surface can host this or a plain PR review works equally well.
5. Deploy with client-side detection only (Agora's pattern) as the default; treat a separate nsite named-site (`d`-tag = `es`) as an optional later add-on, not a day-one requirement.

Confidence: Medium-High — the runtime/parity-gate recommendation is grounded in verified repo evidence; the Lingo.dev pick is grounded in documented product capability but not independently load-tested by this research.

---

## Open Questions

- Whether Lingo.dev's Vite compiler mode has been used in production with a Nostrify/`@nostrify/react` app specifically — no direct evidence found either way, worth a small spike before committing.
- Whether any Nostr client (Ditto or otherwise) actually reads NIP-32 `l`/`L` language tags to drive a UI language filter today, versus the tag existing in the spec but going unused in practice — not independently verified against client source code this session.
- Current status/adoption of eznix86's open i18n/l10n NIP proposal (#1127) beyond "still open as of 2026-03" — worth re-checking periodically since it's the only active path toward a ratified standard.
- Exact current pricing tiers for Lingo.dev at TresPies' likely usage volume — REPORTED as "usage-based" with an OSS/free CLI tier, but exact thresholds weren't independently priced out this session.
- Whether Shakespeare's AI model has actual training exposure to Agora's `src/i18n.ts` (same company, public repo) versus needing the pattern pasted in manually every time — untested this session.

---

## Sources

1. [inlang — Localization Tools](https://inlang.com/c/tools); [opral/paraglide-js (GitHub)](https://github.com/opral/paraglide-js); [Paraglide JS — inlang](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) — accessed 2026-07-09 — MIT license, compiler architecture, bundle-size claim, Fink/Sherlock roles.
2. [Lingo.dev](https://lingo.dev/en); [lingodotdev/lingo.dev (GitHub)](https://github.com/lingodotdev/lingo.dev); [Lingo.dev CLI — How it works](https://lingo.dev/en/cli/how-it-works); [Lingo.dev Compiler — Advanced Configuration](https://lingo.dev/en/compiler/configuration/advanced) — accessed 2026-07-09 — Apache-2.0 license, CLI/CI/compiler mechanics, glossary/brand-voice/quality-scoring "localization engine" model.
3. [Tolgee — Why open-source](https://tolgee.io/opensource); [Tolgee — Self-hosting Licensing](https://docs.tolgee.io/platform/self_hosting/licensing); [Tolgee EE License FAQ](https://tolgee.io/ee-license) — accessed 2026-07-09 — Apache-2.0 core + dual-licensed EE features, free self-host seat limits.
4. [Open-Source TMS Comparison 2026: Weblate vs Tolgee vs Pontoon — IntlPull](https://intlpull.com/blog/open-source-tms-comparison-2026) — accessed 2026-07-09 — Weblate/Tolgee positioning, Crowdin integration-count claim, git-vs-translator-UI framing.
5. [React i18n in 2026: react-intl vs i18next vs LinguiJS — auto18n](https://www.auto18n.com/en/blog/react-i18n-2026); [Best React i18n Libraries in 2026 — Tolgee](https://tolgee.io/blog/react-i18n-libraries-comparison) — accessed 2026-07-09 — bundle-size comparison, extraction-method comparison, ICU-strictness framing.
6. [AI Localization in 2026: Engine, Review, Collaboration — Prismy](https://www.prismy.io/blog/ai-localization-complete-guide); [AI Localization: Automating Content Workflows — Crowdin Blog](https://crowdin.com/blog/ai-localization); [How enterprise teams automate localization with AI — Smartling](https://www.smartling.com/blog/ai-localization-enterprise-workflow) — accessed 2026-07-09 — glossary/brand-voice/quality-routing AI-MT workflow pattern.
7. [gitlab.com/soapbox-pub/ditto](https://gitlab.com/soapbox-pub/ditto) — `package.json` and `src/` tree fetched directly via GitLab API 2026-07-09 — 155 deps, zero i18n-related packages, no locale directory; React 19.2.4/Vite 8/Tailwind/Nostrify confirmed.
8. [gitlab.com/soapbox-pub/armada](https://gitlab.com/soapbox-pub/armada) — `package.json` and `src/` tree fetched directly 2026-07-09 — 99 deps, zero i18n packages, `concord-v1`/`concord-v2` protocol dirs present, no locale directory.
9. [gitlab.com/soapbox-pub/agora](https://gitlab.com/soapbox-pub/agora) — `package.json`, [`src/i18n.ts`](https://gitlab.com/soapbox-pub/agora/-/raw/main/src/i18n.ts), and `src/locales/{en,es}.json` fetched directly 2026-07-09 — i18next stack confirmed, 16 locales, 2,401/2,402 key parity computed programmatically, lazy-chunk architecture read from source comments.
10. [gitlab.com/soapbox-pub/shakespeare](https://gitlab.com/soapbox-pub/shakespeare) — `package.json` and `src/locales/` tree fetched directly 2026-07-09 — i18next stack confirmed, 6 locales (en/ha/ig/pt/yo/zh, no es), 574 leaf keys in `en.json`.
11. [gitlab.com/soapbox-pub/soapbox.pub](https://gitlab.com/soapbox-pub/soapbox.pub) — `package.json` (name: `"mkstack"`) fetched directly 2026-07-09 — zero i18n deps, no locale directory in `src/`.
12. [gitlab.com/soapbox-pub/mkstack-nsp](https://gitlab.com/soapbox-pub/mkstack-nsp) — `README.md`, `CONTEXT.md`, `NIP.md` fetched directly 2026-07-09 — NSP/Deno architecture, `CLONE_URL`-configurable base template, zero language/locale/i18n mentions across the 1,085-line spec.
13. [gitlab.com/soapbox-pub/agora — `AGENTS.md`](https://gitlab.com/soapbox-pub/agora/-/raw/main/AGENTS.md) — accessed 2026-07-09 — verbatim parity-gate behavior and agent-parallelization instruction for translation work.
14. [gitlab.com/soapbox-pub/agora — `AppRouter.tsx`](https://gitlab.com/soapbox-pub/agora/-/raw/main/src/AppRouter.tsx) — accessed 2026-07-09 — confirmed no path-prefix locale routing exists; only a `/settings/language` settings page.
15. [gitlab.com/soapbox-pub/agora — `src/lib/nsiteSubdomain.ts`](https://gitlab.com/soapbox-pub/agora/-/raw/main/src/lib/nsiteSubdomain.ts) — accessed 2026-07-09 — NIP-5A subdomain derivation for root (kind 15128) vs. named (kind 35128, `d`-tag) nsite events, corroborating chapter 06's nsite citation.
16. [NIP-37: Language Tag (PR #632)](https://github.com/nostr-protocol/nips/pull/632) — accessed 2026-07-09 — author Alex Gleason, closed unmerged 2024-06-07, verbatim fiatjaf objection quote.
17. [NIP-32: Labeling](https://github.com/nostr-protocol/nips/blob/master/32.md) — accessed 2026-07-09 — verbatim ISO-639-1 language-labeling example.
18. [NIP-50: Search Capability](https://github.com/nostr-protocol/nips/blob/master/50.md) — accessed 2026-07-09 — `language:<ISO 639-1>` search filter extension.
19. [NIP-C0: Code Snippets](https://github.com/nostr-protocol/nips/blob/master/C0.md) — accessed 2026-07-09 — confirms the `l` tag here means programming language, a distinct namespace from natural-language tagging.
20. [NIP-XXX: Internationalization and Localization (PR #1127)](https://github.com/nostr-protocol/nips/pull/1127) — accessed 2026-07-09 — author eznix86, still open, last activity 2026-03-04, unmerged; proposes `l`-tag plus `kind:0` language field.
