feat(examples): add React Router SPA example - #1946
Open
JoshKappler wants to merge 13 commits into
Open
Conversation
Demonstrates gt-react in a React Router v7 single-page app in framework mode with ssr: false. gt-react initializes once in entry.client.tsx before the router hydrates, then translates content with no provider. Covers <T>, <Num>, <Var>, module-level and in-render t(), useLocale, and <LocaleSelector> across two client-side routes. Wires the @generaltranslation/compiler Vite plugin alongside @react-router/dev, and ships hand-written translation fixtures (es, fr, ja, zh) so locale switching works without any API access.
Contributor
Author
|
Folded into #1949 along with the other three SPA examples to keep the review in one place. The commit and write-up carry over unchanged. |
react-router-spa example, taken from the round-2 review on feat/spa-examples (commit 627189a): - README.md: correct the quickstart clone path to gt/examples/react-router-spa - app/entry.client.tsx: surface hydration errors with main().catch(console.error) instead of void main()
Contributor
Author
|
@greptileai review |
This was referenced Jul 20, 2026
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
JoshKappler
added a commit
that referenced
this pull request
Jul 21, 2026
Addition, take or leave. One of five SPA example PRs from this morning's list (vite #1943, rollup #1945, react-router #1946, rsbuild #1951 on top of the compiler adapter #1950). ## Summary - New example `examples/webpack-spa`: plain React 19 bundled with webpack 5, no framework, client side only. - Runtime story: `initializeGTSPA` entry module (no provider), six `<T>` blocks including a `<Var>` runtime value, one module-level `t()`, `<LocaleSelector>`. - Buildtime story: the compiler's `webpack` export as the first plugin. Credentials come in through dotenv plus DefinePlugin, since webpack has no `import.meta.env`, and production builds inline empty strings so no key ever lands in a production bundle. - Hand-written fixtures under `src/_gt/` keyed by real content hashes; locale switching works offline. `npm run build` runs offline; a separate `translate` script regenerates the files with a production key. ## Testing - typecheck, oxlint, offline production build, frozen-lockfile install: all pass on the current head. All 7 injected `_hash` values match the fixture keys; verified in a real browser across all five locales. - Grepped the production bundle with a planted `GT_DEV_API_KEY` set: the value does not appear (dev builds embed it by design, that is the documented dev workflow on every bundler). - The internal `tests/apps/webpack-react` build passes on this branch (see the postcss note below). - CI lints examples but does not build them, so the build evidence is from manual runs. ## Notes - One non-example change, disclosed plainly: `postcss` is pinned in the workspace root devDependencies. Without it, this example's css-loader pulls postcss into the dependency graph and splits webpack into two peer-keyed instances, which breaks the `tests/apps/webpack-react` build with an instanceof error. The same line ships in #1945 (its postcss plugin has the same effect); whichever merges first carries it and the other becomes a no-op. If there's a preferred way to manage the peer graph, happy to redo this part. - A CodeQL alert flags the dev-key DefinePlugin line. Production builds provably embed nothing (see Testing); dev builds embedding the dev key is the documented design, so the alert needs a dismissal rather than a code change. - `vercel.json` sets `outputDirectory: dist` (webpack gets no Vercel preset) plus an SPA fallback rewrite. - No changeset (example only). <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a new `examples/webpack-spa` directory: a plain React 19 SPA bundled with webpack 5, demonstrating the GT i18n runtime and compiler without any framework. A single non-example change pins `postcss` at the workspace root to prevent a peer-dependency split that broke the existing `tests/apps/webpack-react` build. - **New example**: `initializeGTSPA` entry pattern with top-level `await`, six `<T>` blocks (including a `<Var>` runtime value), one module-level `t()`, and `<LocaleSelector>` — all exercised against hand-written fixture files in `src/_gt/` that enable offline locale switching. - **Credential handling**: `dotenv` + `DefinePlugin` replaces `import.meta.env` (unavailable in webpack); production builds unconditionally inline empty strings so no API key ever lands in a production bundle. - **Workspace fix**: `postcss@^8.5.19` pinned at the repo root resolves a duplicate peer-keyed instance that caused an `instanceof` error in `tests/apps/webpack-react`. <details open><summary><h3>Confidence Score: 5/5</h3></summary> Safe to merge — this is a new example directory plus a targeted workspace-root dependency pin with no changes to library source code. All changes are additive: a self-contained example app and a single postcss pin that fixes an existing peer-dependency conflict. The credential-handling logic is correct (production builds embed empty strings), the GT initialization pattern matches the documented SPA quickstart, and the hand-written fixtures are valid. No library code is modified. No files require special attention. </details> <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | examples/webpack-spa/webpack.config.mjs | Webpack config with GT compiler integration, DefinePlugin for credentials, and react alias for monorepo isolation — correctly gates API keys to dev builds only | | examples/webpack-spa/src/index.ts | Top-level-await entry module that initializes GT before dynamically importing the React app, ensuring module-level t() calls resolve correctly | | examples/webpack-spa/src/loadTranslations.ts | Dynamic locale import with graceful fallback to empty object on missing files; webpack context module will bundle all _gt/*.json at build time | | examples/webpack-spa/src/App.tsx | Demo component exercising T, Var, t(), useLocale, and LocaleSelector with a module-level t() call that relies on GT being initialized before import | | package.json | Pins postcss@^8.5.19 at the workspace root to prevent duplicate peer-keyed postcss instances from breaking the tests/apps/webpack-react build | | examples/webpack-spa/vercel.json | Vercel deployment config with turbo build command, frozen-lockfile install, correct outputDirectory, and SPA catch-all rewrite | </details> <details><summary><h3>Flowchart</h3></summary> <a href="#gh-light-mode-only"> ```mermaid %%{init: {'theme': 'neutral'}}%% flowchart TD A["Browser loads index.html"] --> B["webpack bundle executes src/index.ts"] B --> C["dotenv values injected via DefinePlugin\n(dev: from .env.local, prod: empty strings)"] C --> D["initializeGTSPA()\nawait — blocks until GT is ready"] D --> E{Locale?} E -->|non-default| F["loadTranslations(locale)\nimport('./_gt/locale.json')"] E -->|default/en| G["No translation file needed"] F --> H["webpack context module\nbundles all _gt/*.json at build time"] H --> I["translations returned to GT runtime"] G --> I I --> J["await import('./main')\nReact app mounts AFTER GT init"] J --> K["App.tsx renders\n<T>, t(), <Var>, <LocaleSelector>"] K --> L["Module-level t() resolves correctly\n(GT already initialized)"] ``` </a> <a href="#gh-dark-mode-only"> ```mermaid %%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A["Browser loads index.html"] --> B["webpack bundle executes src/index.ts"] B --> C["dotenv values injected via DefinePlugin\n(dev: from .env.local, prod: empty strings)"] C --> D["initializeGTSPA()\nawait — blocks until GT is ready"] D --> E{Locale?} E -->|non-default| F["loadTranslations(locale)\nimport('./_gt/locale.json')"] E -->|default/en| G["No translation file needed"] F --> H["webpack context module\nbundles all _gt/*.json at build time"] H --> I["translations returned to GT runtime"] G --> I I --> J["await import('./main')\nReact app mounts AFTER GT init"] J --> K["App.tsx renders\n<T>, t(), <Var>, <LocaleSelector>"] K --> L["Module-level t() resolves correctly\n(GT already initialized)"] ``` </a> </details> <sub>Reviews (5): Last reviewed commit: ["fix(examples): correct monorepo install ..."](c959f60) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=45751556)</sub> <!-- /greptile_comment -->
Contributor
Author
|
@greptileai review |
eoinest
reviewed
Jul 21, 2026
| // against loaded translations. Because gt-react reloads the page when the | ||
| // locale changes, this module is re-evaluated on the next load and the value | ||
| // re-resolves for the newly selected locale. | ||
| export const moduleLevelHeading = t( |
Contributor
Author
|
@greptileai review |
eoinest
previously approved these changes
Jul 22, 2026
JoshKappler
added a commit
that referenced
this pull request
Jul 22, 2026
Addition, take or leave. One of five SPA example PRs from this morning's list (webpack #1944, rollup #1945, react-router #1946, rsbuild #1951 on top of the compiler adapter #1950). ## Summary - New example `examples/vite-spa`: the finished product of the React SPA Quickstart plus the "Developing with SPA translations" guide, built to follow the docs faithfully so docs and example can't drift apart. - Runtime story: `src/index.ts` awaits `initializeGTSPA` then dynamically imports the app, so module-level `t()` resolves. No provider. `<T>`, `t()`, and `<LocaleSelector>` all demonstrated. - Buildtime story: the compiler's `vite` plugin with `parsingFlags.devHotReload`, matching vite-create-app. - Hand-written translation files under `src/_gt/` are keyed by the real content hashes, so language switching works offline with no account or API key. `npm run build` runs offline; a separate `translate` script regenerates the files with a production key. - `vercel.json` ships an SPA fallback rewrite so deployed deep links and locale-switch reloads resolve. ## Testing - typecheck, oxlint, offline build, and a frozen-lockfile install all pass on the current head. - Injected `_hash` values in the built bundle match the committed fixture keys, with the compiler on and with it removed (runtime hashing computes the same keys). - Headless Chromium: switching the LocaleSelector rendered en, es, and ja correctly with zero console errors. - CI lints examples but does not build them, so the build evidence above is from manual runs. ## Notes - This is a second Vite example on purpose: vite-create-app shows GT retrofitted onto a create-vite scaffold, vite-spa is the from-scratch docs companion. Both READMEs say which to use. If one Vite example is enough, happy to fold or drop this one. - Docs gap found while building, filed separately: the dev guide never mentions `parsingFlags.devHotReload`, but module-level `t()` strings won't hot-reload without it. - No changeset (example only, matches #1908/#1927). <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a new `examples/vite-spa` example — the finished product of the React SPA Quickstart — demonstrating `gt-react` in a client-side-only Vite app. The runtime story uses top-level `await initializeGTSPA()` before dynamically importing `main.tsx`, so module-level `` t`...` `` calls in `navigation.ts` resolve correctly. Hand-written translation fixture files in `src/_gt/` enable offline switching across four locales without an API key. - **`src/index.ts`**: Imports `gt-react/macros`, awaits `initializeGTSPA` (passing config + env vars), then dynamically imports `./main` — ensuring GT is fully initialized before the component tree renders. - **`src/loadTranslations.ts`**: Dynamic-imports the per-locale JSON at runtime, returning `{}` on any missing locale so the app degrades gracefully. - **Root `.oxlintrc.json`**: Adds `t` as a global `readonly` identifier so the monorepo-wide `no-undef` rule does not flag the implicit `t` macro in `navigation.ts`. <details open><summary><h3>Confidence Score: 5/5</h3></summary> Safe to merge. The new example is a self-contained addition with no changes to production library code, and the one shared-config tweak is additive. All source changes are confined to the new examples/vite-spa directory. The only repo-wide modification is adding t to the oxlint globals block, which is additive and doesn't affect runtime behavior or library packages. The initialization sequence (macros → initializeGTSPA → dynamic import of main) is architecturally sound, offline build is verified, and the lockfile correctly reflects the package.json dependency placement. .oxlintrc.json — the t global is broader than needed; worth scoping to SPA examples via an override, but not a blocker. </details> <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | examples/vite-spa/src/index.ts | Entry module: correctly sequences macro import, initializeGTSPA await, and dynamic app import to guarantee GT is ready before any module-level t`` calls execute. | | examples/vite-spa/src/navigation.ts | Uses implicit global t`` macro (attached by gt-react/macros in index.ts); intentional SPA-only pattern, well-documented in README. | | examples/vite-spa/src/loadTranslations.ts | Dynamic-imports locale JSON from src/_gt/; returns {} on missing locale. console.warn is explicitly allowed by the no-console lint rule. | | .oxlintrc.json | Adds 't' as a monorepo-wide readonly global so CI no-undef checks pass for navigation.ts; broader than strictly necessary since t is only a valid implicit global in SPA entry contexts. | | examples/vite-spa/vite.config.ts | Wires react() and gtCompiler(gtConfig) plugins; dedupe for react/react-dom handles workspace-link resolution correctly. | | examples/vite-spa/vercel.json | Uses turbo build command with --env-mode=loose; includes SPA fallback rewrite; matches conventions of sibling examples. | | examples/vite-spa/package.json | gt is correctly placed under devDependencies (matches its lockfile placement); gt-react in dependencies; all workspace:* pins consistent. | | examples/vite-spa/gt.config.json | Configures defaultLocale, four target locales, output path, and parsingFlags.devHotReload for hot-reload of module-level t`` strings during development. | </details> <details><summary><h3>Sequence Diagram</h3></summary> ```mermaid sequenceDiagram participant Browser participant index.ts participant loadTranslations.ts participant _gt/[locale].json participant initializeGTSPA participant main.tsx participant App Browser->>index.ts: load (module script) index.ts->>index.ts: import 'gt-react/macros' (attaches global t``) index.ts->>initializeGTSPA: "await initializeGTSPA({ ...gtConfig, projectId, devApiKey, loadTranslations })" initializeGTSPA->>loadTranslations.ts: call loadTranslations(locale) loadTranslations.ts->>_gt/[locale].json: dynamic import _gt/[locale].json-->>loadTranslations.ts: translation map loadTranslations.ts-->>initializeGTSPA: resolved translations initializeGTSPA-->>index.ts: resolved index.ts->>main.tsx: await import('./main') main.tsx->>App: createRoot().render(App) App->>App: navigation uses t`Home`, t`About` (resolved) App->>App: Welcome renders T and LocaleSelector ``` </details> <sub>Reviews (7): Last reviewed commit: ["fix(examples): use the global t macro an..."](3535245) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=45751551)</sub> <!-- /greptile_comment -->
JoshKappler
added a commit
that referenced
this pull request
Jul 22, 2026
Addition, take or leave. One of five SPA example PRs from this morning's list (vite #1943, webpack #1944, react-router #1946, rsbuild #1951 on top of the compiler adapter #1950). ## Summary - New example `examples/rollup-spa`: plain React 19 bundled with pure Rollup, wired from the proven `tests/apps/rollup-react` config (compiler's `rollup` export first, then replace, nodeResolve, commonjs, json, typescript, postcss, and a small guarded html emitter with a single shared CSS path constant). - Runtime story: `initializeGTSPA` entry module, `<T>`, `<Var>`, module-level `t()`, `<LocaleSelector>`. - `loadTranslations` uses an explicit per-locale static import map instead of the docs' interpolated dynamic import, because Rollup can't code-split a fully dynamic path. Each locale still gets its own lazy chunk, unknown locales warn, and the README explains the divergence. - Dev workflow: `rollup -c -w` with serve and livereload, dotenv wired so the documented `.env` step actually loads. `npm run build` runs offline; a separate `translate` script regenerates the translation files with a production key. ## Testing - typecheck, oxlint, offline build, frozen-lockfile install: all pass on the current head. Injected `_hash` values match the fixture keys; verified in a real browser across en, fr, ja including a `<Var>` mid-sentence. - The dotenv wiring was proven dynamically: a throwaway `.env` value inlines into the bundle and disappears on a clean rebuild. - The internal `tests/apps/webpack-react` build passes on this branch (see the postcss note below). - CI lints examples but does not build them. ## Notes - One non-example change, disclosed plainly: `postcss` is pinned in the workspace root devDependencies so every webpack consumer resolves a single instance. Without it, this example's postcss plugin splits webpack's peer context and breaks the `tests/apps/webpack-react` build. The same line ships in #1944; whichever merges first carries it and the other becomes a no-op. If there's a preferred way to manage the peer graph, happy to redo this part. - Honest limitation, documented in the README: on-the-fly dev translation fetching does not activate under plain Rollup, because nothing supplies gt-react an env signal in the browser. The committed files are what renders. A dev define injected by the compiler's rollup plugin would fix this generally; that would be a library change. - Lockfile note: installing this example dedupes gt-sanity's build toolchain from rollup 4.60.3 to 4.62.2 (semver-minor, reproducible, frozen installs pass). The gt-sanity build failure visible locally is pre-existing on clean main and unrelated. - No changeset (example only). <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a new `examples/rollup-spa` example: a React 19 SPA bundled with pure Rollup and wired with `gt-react` for build-time i18n via the GT compiler plugin. It is the Rollup counterpart to the concurrent Vite/Webpack/RSBuild SPA PRs. - **Build pipeline** (`rollup.config.mjs`): GT compiler runs first, followed by `replace`, `nodeResolve`, `commonjs`, `json`, `typescript`, `postcss` (extraction), and a small custom `htmlPlugin`. The CSS extract path is shared via `CSS_ASSET_PATH` to prevent drift between the PostCSS target and the injected `<link>` href. - **Translation loading** (`src/loadTranslations.ts`): Uses an explicit per-locale static import map instead of a template-literal dynamic import, because Rollup cannot statically analyze a fully dynamic path; each entry produces its own lazy chunk. - **Non-example change** (`package.json` root): `postcss` is pinned as a workspace-root devDependency to ensure a single peer instance across Webpack consumers; an identical change ships in the sibling #1944 PR. <details open><summary><h3>Confidence Score: 5/5</h3></summary> This is a well-isolated example addition; no production library code is touched and the one non-example change (pinning postcss at the workspace root) is a safe deduplication. All changes are scoped to a new example directory plus two one-line updates to workspace config files. The Rollup pipeline is correct, the CSS path constant properly guards the extract/inject pair, and the translation loader map matches the locales declared in gt.config.json. No files require special attention; the pnpm-lock.yaml churn is expected from the rollup deduplication described in the PR notes. </details> <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | examples/rollup-spa/rollup.config.mjs | Rollup build config with correct plugin order, shared CSS_ASSET_PATH constant, and guarded isWatch dev-server conditional; NODE_ENV defaults to 'production' in watch mode (no explicit dev override) | | examples/rollup-spa/src/loadTranslations.ts | Explicit static-import locale map that lets Rollup code-split per-locale chunks; returns {} with a console.warn for unknown locales; 'en' (default locale) is correctly absent since the SPA runtime uses source strings for it | | examples/rollup-spa/src/index.ts | Entry module using top-level await to sequence initializeGTSPA before React mounts; gt-react/macros import attaches the global t macro; correct ESM pattern for this setup | | examples/rollup-spa/src/App.tsx | Demonstrates T component, Var mid-sentence, and module-level t macro; locale switch triggers full page reload as expected for gt-react SPA mode | | examples/rollup-spa/vercel.json | Vercel deployment config using turbo build with --env-mode=loose, SPA fallback rewrite to index.html; consistent with other example deployments in the monorepo | | .oxlintrc.json | Adds t as a readonly global to prevent undefined-variable lint errors from the module-level t macro used by the SPA examples | | .changeset/config.json | Adds the five new SPA example package names to the changeset ignore list; correct for example-only packages | </details> <details><summary><h3>Sequence Diagram</h3></summary> ```mermaid sequenceDiagram participant Browser participant index.ts as src/index.ts participant GTRuntime as initializeGTSPA (gt-react) participant loadTranslations as loadTranslations.ts participant JSON as _gt/<locale>.json chunk Browser->>index.ts: load ESM bundle index.ts->>GTRuntime: "await initializeGTSPA({ projectId, loadTranslations })" GTRuntime->>loadTranslations: loadTranslations(currentLocale) alt locale found in map (zh/fr/es/ja) loadTranslations->>JSON: "dynamic import('./_gt/<locale>.json')" JSON-->>loadTranslations: translations object loadTranslations-->>GTRuntime: translations else locale not in map loadTranslations-->>GTRuntime: "{} (+ console.warn)" end GTRuntime-->>index.ts: runtime ready index.ts->>index.ts: await import('./main') index.ts->>Browser: React tree mounts with translations ``` </details> <sub>Reviews (7): Last reviewed commit: ["fix(examples): use the global t macro in..."](7b6105c) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=45751569)</sub> <!-- /greptile_comment -->
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addition, take or leave. One of five SPA example PRs (vite #1943, webpack #1944, rollup #1945, rsbuild #1951 on the compiler adapter #1950).
Summary
examples/react-router-spa: React Router v7 framework mode withssr: false, two client routes, gt-react wired throughinitializeGTSPA.app/entry.client.tsxawaitsinitializeGTSPAand only then dynamically imports the hydrate step, so no route module (and no module-levelt()) evaluates before translations are ready.root.tsxstays gt-free so the prerendered shell never touches gt-react.<T>,<Num>,<Var>, module-level and in-rendert(),<LocaleSelector>, and anHtmlLangSyncthat keeps<html lang>anddiron the active locale, which the static shell cannot do.npm run buildruns offline; a separatetranslatescript regenerates the translation files with a production key.Testing
build/client/index.html), frozen-lockfile install: all pass. CI lints examples but does not build them.Notes
vercel.jsoncarries SPA fallback rewrites. Without them a deployed app 404s on deep links and on every locale switch from a subroute, since gt-react switches locale vialocation.reload().@react-router/nodeandisbotlook unused but are required: withssr: falseand no custom entry.server, the toolchain generates a server entry importing both to prerender the shell.Greptile Summary
Adds a new
examples/react-router-spademonstrating gt-react internationalization in a React Router v7 SPA (framework mode,ssr: false). The key design — awaitinginitializeGTSPAinentry.client.tsxbefore dynamically importing the hydrate step — guarantees that no route module (and no module-levelt()) evaluates before translations are ready, which is clearly explained and correctly implemented.entry.client.tsxgates hydration behindinitializeGTSPA, andmain().catch(console.error)ensures initialization failures are surfaced rather than silently dropped.HtmlLangSyncupdateshtml[lang]andhtml[dir]from route context, cleanly working around the build-time prerender constraint inroot.tsx.gt(a CLI-only tool) is placed independenciesrather thandevDependencies, inconsistent with the analogousvite-spaexample where it correctly lives indevDependencies.Confidence Score: 5/5
Safe to merge — this is a self-contained example with no changes to library code or shared infrastructure.
The initialization sequencing is correct, the prerender/hydration split is well-reasoned, and the only finding is a minor dependency categorization mismatch that has no runtime impact.
Files Needing Attention: package.json — gt belongs in devDependencies (CLI-only tool), consistent with vite-spa.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Browser participant entry.client.tsx participant initializeGTSPA participant loadTranslations participant hydrate.tsx participant HydratedRouter participant RouteModule Browser->>entry.client.tsx: module evaluation (import 'gt-react/macros') entry.client.tsx->>initializeGTSPA: "await initializeGTSPA({ loadTranslations, ... })" initializeGTSPA->>loadTranslations: loadTranslations(locale) loadTranslations-->>initializeGTSPA: "locale JSON (app/_gt/<locale>.json)" initializeGTSPA-->>entry.client.tsx: resolved (translations ready) entry.client.tsx->>hydrate.tsx: dynamic import('./hydrate') hydrate.tsx->>HydratedRouter: hydrateRoot(document, HydratedRouter) HydratedRouter->>RouteModule: lazy load home.tsx / about.tsx RouteModule->>RouteModule: module-level t() in messages.ts resolves correctly RouteModule-->>Browser: rendered UI with translationsReviews (12): Last reviewed commit: "chore: trim comments to the 3-line house..." | Re-trigger Greptile