A suite of boilerplate apps for testing the General Translation i18n libraries against local gt worktrees.
This is not a monorepo. Each app under base/ is a fully standalone
pnpm + TypeScript + ESM project with its own lockfile. The root only carries
convenience scripts.
| App | Framework | Entry package | What it exercises |
|---|---|---|---|
base/next-app-router |
Next.js App Router | gt-next |
RSC + SSR, server/client component split, getGT |
base/next-app-router-locale-routing |
Next.js App Router | gt-next |
Same as above, plus /[locale] routing |
base/next-app-router-locale-routing-ssg |
Next.js App Router | gt-next |
Locale routing with generateStaticParams / SSG |
base/next-app-router-locale-routing-use-cache |
Next.js App Router | gt-next |
Locale routing with Cache Components / "use cache" |
base/next-pages-router |
Next.js Pages Router | gt-next |
Pages SSR, gt-next client entry points |
base/tanstack-start |
TanStack Start | gt-tanstack-start |
Selective SSR: SSR, SPA (ssr: false), data-only |
base/react-native |
Expo + React Native | gt-react-native |
Native app provider, hooks, components, plugin |
base/vite-react |
Vite + React (SPA) | gt-react |
Pure client-side gt-react |
base/webpack-react |
webpack + React (SPA) | gt-react |
Same SPA source bundled with webpack |
base/rollup-react |
Rollup + React (SPA) | gt-react |
Same SPA source bundled with Rollup |
base/rolldown-react |
Rolldown + React (SPA) | gt-react |
Same SPA source bundled with Rolldown |
base/esbuild-react |
esbuild + React (SPA) | gt-react |
Same SPA source bundled with esbuild |
TanStack Start does not ship stable RSC support yet; the App Router app is the RSC test bed. The TanStack app covers the three selective-SSR rendering modes (
ssr: true,ssr: false,ssr: 'data-only').
pnpm install:all # pnpm install in every app (registry packages)
pnpm build:all # build every appOr per app: pnpm --dir base/<app> install, pnpm dev:<app>, pnpm build:<app>.
The React Native app's build script runs TypeScript validation; use
pnpm dev:react-native for the Expo development server.
Build the worktree first — linking points the apps at its dist/ output:
cd ~/Documents/dev/gt-wt/<your-worktree>
pnpm install && pnpm buildThen from this repo's root:
pnpm gt:link <app|all> <path-to-worktree>
# e.g.
pnpm gt:link next-app-router ~/Documents/dev/gt-wt/e-odysseus-fix-server-exports
pnpm gt:link all ~/Documents/dev/gt-wt/e-test-gt-test-apps
pnpm gt:status # show what each app is linked to
pnpm gt:unlink all # restore baseline specs everywherepnpm gt:link rewrites the app's gt dependencies to
link: protocol
specs pointing into the worktree, then runs pnpm install. This is the same
mechanism as the hand-maintained playground apps — not global pnpm link.
Per framework:
-
React SPAs / TanStack Start — direct app gt dependencies are rewritten to
link:specs, and transitive gt workspace packages are forced with pnpmoverridesso context/state singletons stay single. The React SPA variants directly linkgt-reactand@generaltranslation/compiler, while TanStack directly linksgt-tanstack-start. -
React Native — the Expo app directly links
gt-react-nativeand uses pnpmoverridesfor the shared gt workspace packages. Its Metro config watches linked package realpaths and keeps React / React Native resolved from the app install. -
Next.js — Next is pickier. The script rewrites each app's direct
gt-nextdependency and writes pnpmoverridesinto the app'spnpm-workspace.yamlfor the rest of gt-next's workspace package graph. The base App Router app also declaresgt-react,generaltranslation,@generaltranslation/react-core, and@generaltranslation/formatdirectly, so those entries are rewritten there too. Each app'snext.config.tsdetects linked packages at runtime to configure Turbopack:turbopack.rootis widened to a common ancestor of the app and the worktree (linked sources live outside the app directory);turbopack.resolveAliaspins the linked packages' third-party deps (@formatjs/icu-messageformat-parser,@noble/hashes,intl-messageformat) to this app's ownnode_modulescopies — Turbopack otherwise fails to resolve them through the worktree symlinks. The apps carry those three as direct dependencies for exactly this reason.
When the apps are unlinked, the runtime detection in
next.config.tsis a no-op, so the baseline behaves like a stock quickstart app. The SSG and Cache Components locale-routing variants use the same linking recipe as the base locale-routing app.
After validating, restore the committed baseline (lockfiles included) with:
pnpm gt:unlink all # rewrites specs + reinstalls
git checkout -- base # restore lockfiles byte-for-byteThe bar for "linking works" is: pnpm build:<app> and pnpm dev:<app> run
with no module-not-found / export-not-found errors. Translation content is
intentionally minimal — each app ships empty fr/zh translation files and a
loadTranslations that falls back to {}, so no GT API keys are needed.
To test real translations, add API keys (see each app's .env.example if
present, or the quickstart docs) and run
npx gt translate inside an app.
See AGENTS.md for vendor-neutral agent guidance and
agents/link-apps.md for the per-framework linking
deep-dive. Claude Code users also have a /link-app skill.
gt-tanstack-start's API surface is evolving quickly, so the app imports gt symbols fromgt-tanstack-startdirectly. Keep provider/hooks/components on the same entry package when adjusting TanStack tests.- The Pages Router app uses
gt-react'sGTProviderin_app.tsx(the pattern from the official example).pages/index.tsxcontains a commented-outgetTranslationsSnapshotblock for branches that ship the new pages SSR entry points. - Baseline package specs live in
scripts/gt-link.ts(GT_PACKAGES). Keep them aligned with the committed app manifests.