Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8c52eb5
feat(tanstack-start): add router integration and Vite plugin
chenxin-yan Oct 8, 2026
e066484
refactor(tanstack-start): rename plugin to gtTanstackStart and take r…
chenxin-yan Oct 8, 2026
dd64c00
feat(tanstack-start): rewrite locale prefixes when localeRouting is e…
chenxin-yan Oct 8, 2026
de4fb10
fix(compiler): track gt-tanstack-start imports like gt-react
chenxin-yan Oct 8, 2026
061b248
feat(tanstack-start): run the GT compiler from gtTanstackStart()
chenxin-yan Oct 8, 2026
54a57df
feat(cli): configure TanStack Start with the Vite plugin and router i…
chenxin-yan Oct 8, 2026
14a28bc
docs(tanstack-start): document the plugin and router integration setup
chenxin-yan Oct 8, 2026
08284f4
fix(tanstack-start): share server locale routing and request locale a…
chenxin-yan Oct 8, 2026
b84643b
fix(tanstack-start): resolve translation output like the gt CLI and r…
chenxin-yan Oct 8, 2026
5aa1778
fix(cli): tighten TanStack Start setup detection and loader maintenance
chenxin-yan Oct 8, 2026
69baf87
fix(cli): require an unambiguous GT plugin and router integration in …
chenxin-yan Oct 8, 2026
c241380
feat(tanstack-start): make the GT compiler opt-in like gt-next
chenxin-yan Oct 9, 2026
fb36b11
fix(tanstack-start): keep server-only code and React 19 use out of pa…
chenxin-yan Oct 9, 2026
4fa68cd
fix(tanstack-start): reject custom loaders without a loadTranslations…
chenxin-yan Oct 9, 2026
fa76092
test(tanstack-start): cover request condition caching without middleware
chenxin-yan Oct 9, 2026
18d63c6
docs(tanstack-start): show useLocale in the root route shell
chenxin-yan Oct 9, 2026
1421e80
fix(tanstack-start): warn that gt.config.json edits need a dev server…
chenxin-yan Oct 9, 2026
c827346
fix(cli): check the TanStack Start plugin config path and loader export
chenxin-yan Oct 9, 2026
abf7d75
fix(tanstack-start): say a gt.config.json change needs a full dev ser…
chenxin-yan Oct 9, 2026
e3ffe99
fix(tanstack-start): serve one gt.config.json snapshot per process
chenxin-yan Oct 9, 2026
e7b1f95
refactor(tanstack-start): share the router Wrap between server and cl…
chenxin-yan Oct 9, 2026
864094b
chore(examples): use the recommended gt-tanstack-start setup
chenxin-yan Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cli-tanstack-start-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'gt': minor
---

`gt init` sets up TanStack Start apps with the `gtTanstackStart()` Vite plugin and `setupRouterGTIntegration({ router })` instead of editing `src/start.ts`, the root route, and a generated `loadTranslations` file. Apps already using the previous setup are left unchanged.
5 changes: 5 additions & 0 deletions .changeset/compiler-tanstack-start-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@generaltranslation/compiler': patch
---

The compiler now handles `<T>`, `useGT`, `t`, and other GT imports from `gt-tanstack-start` the same way it handles imports from `gt-react`.
7 changes: 7 additions & 0 deletions .changeset/tanstack-start-router-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'gt-tanstack-start': minor
---

TanStack Start setup now takes two lines: add `gtTanstackStart()` from `gt-tanstack-start/plugin/vite` to your Vite plugins, and call `setupRouterGTIntegration({ router })` in `getRouter()`. The plugin reads `gt.config.json` and generates the translation loader from `files.gt.output`. Like gt-next, it can also run the experimental GT compiler: install `@generaltranslation/compiler` and set `experimentalCompilerOptions: { type: 'babel' }`. The router integration loads translations during SSR, hydrates them on the client, and renders `GTProvider` for you, so apps no longer need `initializeGT()`, a `loadTranslations` file, a root-route loader, `<GTProvider>`, or `gtMiddleware`. The previous manual setup keeps working.

When `localeRouting` is enabled, `setupRouterGTIntegration` also installs a router URL rewrite: routes and links stay locale-free, non-default locales are prefixed in the URL, and the default locale stays unprefixed. If your app already handles locale prefixes with `{-$locale}` route segments or its own rewrite, pass `localeRewrite: false` to keep your setup.
90 changes: 57 additions & 33 deletions packages/cli/src/cli/__tests__/initOnboarding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,29 +432,14 @@ describe('init and configure onboarding', () => {
name: 'tanstack-start',
type: 'react',
});
fs.mkdirSync(file('src/routes'), { recursive: true });
fs.mkdirSync(file('src'), { recursive: true });
fs.writeFileSync(
file('src/router.tsx'),
"import { createRouter } from '@tanstack/react-router'\n\nexport function getRouter() {}\n"
file('vite.config.ts'),
"import { defineConfig } from 'vite'\n\nexport default defineConfig({\n plugins: [],\n})\n"
);
fs.writeFileSync(
file('src/routes/__root.tsx'),
`import { createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
shellComponent: RootDocument,
})

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
</body>
</html>
)
}
`
file('src/router.tsx'),
"import { createRouter } from '@tanstack/react-router'\n\nexport function getRouter() {\n const router = createRouter({ routeTree })\n return router\n}\n"
);
});

Expand All @@ -481,24 +466,21 @@ function RootDocument({ children }: { children: React.ReactNode }) {
});
expect(fs.existsSync(file('loadTranslations.js'))).toBe(false);
expect(fs.readFileSync(file('src/router.tsx'), 'utf8')).toContain(
'initializeGT({ ...gtConfig, loadTranslations })'
' setupRouterGTIntegration({ router })\n return router'
);
expect(fs.readFileSync(file('src/routes/__root.tsx'), 'utf8')).toContain(
'<GTProvider locale={locale} translations={translations}>'
);
expect(fs.readFileSync(file('src/start.ts'), 'utf8')).toContain(
'requestMiddleware: [csrfMiddleware, gtMiddleware]'
expect(fs.readFileSync(file('vite.config.ts'), 'utf8')).toContain(
'plugins: [gtTanstackStart()]'
);
expect(fs.existsSync(file('src/loadTranslations.ts'))).toBe(false);
expect(fs.existsSync(file('src/start.ts'))).toBe(false);
expect(fs.readFileSync(file('src/_gt/es.json'), 'utf8')).toBe('{}\n');
expect(events().at(-1)).toMatchObject({
outcome: 'success',
completedSteps: [
'installed gt-tanstack-start',
'created gt.config.json',
'created src/loadTranslations.ts',
'created src/start.ts',
'configured vite.config.ts',
'configured src/router.tsx',
'configured src/routes/__root.tsx',
'installed gt',
],
});
Expand Down Expand Up @@ -527,8 +509,48 @@ function RootDocument({ children }: { children: React.ReactNode }) {
expect(events().at(-1)).toMatchObject({ outcome: 'success' });
});

it('stops before any change when the root route is missing', async () => {
fs.rmSync(file('src/routes/__root.tsx'));
// The Vite plugin loads translations with an existing loader over the CDN.
it('names a leftover loader after configure switches to the CDN', async () => {
await run(
'init',
'--json',
'--defaults',
'--locales',
'fr',
'--no-live-translations'
);
fs.writeFileSync(
file('src/loadTranslations.ts'),
'export default async () => ({});\n'
);
// installPackage is mocked, so record the runtime init installed.
const packageJson = JSON.parse(
fs.readFileSync(file('package.json'), 'utf8')
);
packageJson.dependencies['gt-tanstack-start'] = '*';
fs.writeFileSync(file('package.json'), JSON.stringify(packageJson));
stdoutEvents = [];

await run(
'configure',
'--json',
'--defaults',
'--no-dev-credentials',
'--storage',
'cdn'
);

expect(readConfig().files?.gt).toBeUndefined();
expect(events().at(-1)).toMatchObject({
outcome: 'needs_human_action',
actions: [
'Delete src/loadTranslations.ts so translations load from the CDN',
],
});
});

it('stops before any change when the Vite config is missing', async () => {
fs.rmSync(file('vite.config.ts'));

await expect(
run(
Expand All @@ -539,15 +561,17 @@ function RootDocument({ children }: { children: React.ReactNode }) {
'fr',
'--no-live-translations'
)
).rejects.toThrow('src/routes/__root.tsx was not found');
).rejects.toThrow('vite.config.ts was not found');

expect(events().at(-1)).toMatchObject({
outcome: 'failed',
completedSteps: [],
});
expect(installPackage).not.toHaveBeenCalled();
expect(fs.existsSync(file('gt.config.json'))).toBe(false);
expect(fs.existsSync(file('src/start.ts'))).toBe(false);
expect(fs.readFileSync(file('src/router.tsx'), 'utf8')).not.toContain(
'setupRouterGTIntegration'
);
});
});

Expand Down
9 changes: 4 additions & 5 deletions packages/cli/src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1554,12 +1554,11 @@ See https://www.npmjs.com/package/gt-vue`);
!reactSetup &&
configuredTranslationsDir !== undefined
) {
// Only the application setup rewrites the initializer; a loader passed
// to it takes precedence over CDN loading.
reportManualAction({
whatHappened: `Translations now load from the CDN, but ${buildTool.initializer} may still receive the local loader for ${configuredTranslationsDir}`,
fix: `Remove the loadTranslations option and its import from the ${buildTool.initializer}() call so translations load from the CDN`,
const actions = await buildTool.getCdnSwitchActions({
...buildToolContext,
previousTranslationsDir: configuredTranslationsDir,
});
actions.forEach(reportManualAction);
} else if (
storage === 'cdn' &&
!buildTool &&
Expand Down
Loading
Loading