From c244129a03a41c8f4beb70fb5dc5852bb773dbaf Mon Sep 17 00:00:00 2001 From: JoshKappler Date: Tue, 21 Jul 2026 11:08:52 -0700 Subject: [PATCH 1/3] docs(react): match quickstart paths to the create-react-router scaffold The page's src/ paths do not exist in the app/ tree the scaffold creates, so a literal walk boots with GT silently absent. Use app/ paths, name the scaffold command, merge the two root.tsx steps into one compilable file, mount the built components in a route, and make html lang follow the resolved locale. Node floor comes from react-router's engines field. --- docs/en-US/react/react-quickstart.mdx | 96 ++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 18 deletions(-) diff --git a/docs/en-US/react/react-quickstart.mdx b/docs/en-US/react/react-quickstart.mdx index 6c2b1ee77..d59bbe068 100644 --- a/docs/en-US/react/react-quickstart.mdx +++ b/docs/en-US/react/react-quickstart.mdx @@ -14,8 +14,10 @@ related: By the end of this guide, your server-rendered React app will display content in multiple languages, with a language switcher your users can interact with. **Prerequisites:** -- A server-rendered React app (React Router or a custom SSR setup) -- Node.js 18+ +- A server-rendered React app (React Router or a custom SSR setup). To start fresh, run `npx create-react-router@latest my-app`. +- Node.js 22.22+ (the floor the current React Router packages declare) + +This guide uses the `app/` directory that `create-react-router` scaffolds. If your SSR setup keeps code somewhere else, such as `src/`, adjust the paths as you go. **Note:** If your app renders entirely in the browser with Vite, follow the [React SPA Quickstart](/docs/react/react-spa-quickstart) instead. It skips the provider entirely. @@ -66,7 +68,7 @@ Create a **`gt.config.json`** file in your project root. This tells the library "locales": ["es", "fr", "ja"], "files": { "gt": { - "output": "src/_gt/[locale].json" + "output": "app/_gt/[locale].json" } } } @@ -81,7 +83,7 @@ Create a **`gt.config.json`** file in your project root. This tells the library Create a `loadTranslations` function that loads a locale's translation file. On the server this runs during rendering; the CLI generates the files when you run `npx gt translate`: -```ts title="src/loadTranslations.ts" +```ts title="app/loadTranslations.ts" export default async function loadTranslations(locale: string) { try { const translations = await import(`./_gt/${locale}.json`); @@ -95,12 +97,12 @@ export default async function loadTranslations(locale: string) { ### 4. Initialize the library -Call **`initializeGT`** at module scope in a file that loads on both the server and the client — your root route or layout is the natural place. It registers your config and translation loader once; the configuration is immutable for the lifetime of the app: +Call **`initializeGT`** at module scope in a file that loads on both the server and the client. In a React Router app that file is `app/root.tsx`. It registers your config and translation loader once; the configuration is immutable for the lifetime of the app: -```tsx title="src/routes/root.tsx" +```tsx title="app/root.tsx" import { initializeGT } from 'gt-react'; -import gtConfig from '../../gt.config.json'; -import loadTranslations from '../loadTranslations'; +import gtConfig from '../gt.config.json'; +import loadTranslations from './loadTranslations'; initializeGT({ defaultLocale: gtConfig.defaultLocale, @@ -112,17 +114,37 @@ initializeGT({ ### 5. Load translations on the server -In your root route's loader (or equivalent server handler), resolve the request locale and fetch a translations snapshot with **`getTranslationsSnapshot`**, then pass both to **``**: +In the same `app/root.tsx`, add a loader that resolves the request locale and fetches a translations snapshot with **`getTranslationsSnapshot`**, then pass both to **``**. Keep the `initializeGT` call from Step 4, and keep the scaffold's `links` and `ErrorBoundary` exports as generated; the block below shows the rest of the file: -```tsx title="src/routes/root.tsx" +```tsx title="app/root.tsx" +import { + isRouteErrorResponse, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, + useLoaderData, + useRouteLoaderData, +} from 'react-router'; import { GTProvider, getTranslationsSnapshot, + initializeGT, parseLocale, } from 'gt-react'; +import type { Route } from './+types/root'; +import './app.css'; +import gtConfig from '../gt.config.json'; +import loadTranslations from './loadTranslations'; + +initializeGT({ + defaultLocale: gtConfig.defaultLocale, + locales: gtConfig.locales, + loadTranslations, +}); -// In your route loader (exact API depends on your framework) -export async function loader({ request }) { +export async function loader({ request }: Route.LoaderArgs) { const locale = parseLocale(request); // [!code highlight] return { locale, @@ -130,22 +152,44 @@ export async function loader({ request }) { }; } -export default function Root({ children }) { - const { locale, translations } = useLoaderData(); +export function Layout({ children }: { children: React.ReactNode }) { + const locale = + useRouteLoaderData('root')?.locale ?? gtConfig.defaultLocale; + return ( + + + + + + + + + {children} + + + + + ); +} + +export default function App() { + const { locale, translations } = useLoaderData(); return ( - {children} + ); } ``` +The `Layout` change makes `` follow the resolved locale instead of the scaffold's hardcoded `"en"`, so screen readers and search engines see the language your users actually get. The `./+types/root` import and `Route.LoaderArgs` come from React Router's typegen, which runs on the first `react-router dev`, `build`, or `npm run typecheck`; run one of those before type-checking a fresh scaffold. In a custom SSR setup, do the same work in whatever code runs per request: resolve the locale with `parseLocale`, fetch the snapshot, and wrap your app in the provider, typing the loader argument yourself. + ### 6. Mark content for translation Wrap any text you want translated with the **``** component. `` stands for "translate": -```tsx title="src/components/Welcome.tsx" +```tsx title="app/components/Welcome.tsx" import { T } from 'gt-react'; export default function Welcome() { @@ -162,7 +206,7 @@ export default function Welcome() { For plain strings — like `placeholder` attributes or `aria-label` values — use the **`useGT`** hook: -```tsx title="src/components/ContactForm.tsx" +```tsx title="app/components/ContactForm.tsx" import { useGT } from 'gt-react'; export default function ContactForm() { @@ -176,7 +220,7 @@ export default function ContactForm() { Drop in a **``** so users can change languages: -```tsx title="src/components/Header.tsx" +```tsx title="app/components/Header.tsx" import { LocaleSelector } from 'gt-react'; export default function Header() { @@ -186,6 +230,22 @@ export default function Header() { When the user picks a language, `gt-react` persists the choice in the `generaltranslation.locale` cookie and reloads the page, so the server re-renders everything in the new locale. +The components from Steps 6 and 7 render only once a route uses them. Update your home route to mount them: + +```tsx title="app/routes/home.tsx" +import Welcome from '../components/Welcome'; +import Header from '../components/Header'; + +export default function Home() { + return ( + <> +
+ + + ); +} +``` + ### 8. Set up environment variables (optional) From fc6cb7dfd4eb68e39130396fb75c3900d6ea11cf Mon Sep 17 00:00:00 2001 From: JoshKappler Date: Tue, 21 Jul 2026 11:08:52 -0700 Subject: [PATCH 2/3] docs(react-spa): render the built component and keep the scaffold css The quickstart builds Welcome.tsx but nothing imports it, so the promised result never renders. Add the App.tsx wiring, restore the index.css import the main.tsx block dropped, name a scaffold command, and raise the stale Node 18 prerequisite to the ranges Vite 8 supports. --- docs/en-US/react/react-spa-quickstart.mdx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/en-US/react/react-spa-quickstart.mdx b/docs/en-US/react/react-spa-quickstart.mdx index 4cc4a2cf8..f0a513a08 100644 --- a/docs/en-US/react/react-spa-quickstart.mdx +++ b/docs/en-US/react/react-spa-quickstart.mdx @@ -17,8 +17,8 @@ In a single-page app, `gt-react` runs entirely in the browser — you initialize **Prerequisites:** -- A client-side rendered React app (Vite, webpack, or similar) -- Node.js 18+ +- A client-side rendered React app (Vite, webpack, or similar). To start fresh, run `npm create vite@latest my-app -- --template react-ts`. +- Node.js 20.19+ or 22.12+ (the ranges the current Vite scaffold supports) **Tip:** Run `npx gt@latest` to configure everything with the [Setup Wizard](/docs/cli/reference/commands/init). This guide covers manual setup. @@ -165,9 +165,12 @@ start().catch(console.error); +Your `src/main.tsx` stays as the scaffold created it (keep its `./index.css` import): + ```tsx title="src/main.tsx" import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import './index.css'; import App from './App'; createRoot(document.getElementById('root')!).render( @@ -214,6 +217,16 @@ export default function Welcome() { You can wrap as much or as little JSX as you want inside ``. Everything inside it — text, nested elements, even formatting — gets translated as a unit. +Then render the component from your `App.tsx` so it appears in the app: + +```tsx title="src/App.tsx" +import Welcome from './components/Welcome'; + +export default function App() { + return ; +} +``` + For strings outside React components, use **`t()`**. It works at module level because `initializeGTSPA()` loads translations before the rest of your app: ```ts title="src/navigation.ts" @@ -259,7 +272,7 @@ Before translating, authenticate with General Translation: npx gt auth ``` -Follow the prompts to create an account or log in. When prompted for a key type, choose a production key. The command generates an API key and project ID, then adds them to `.env.local` in your project root: +Follow the prompts to create an account or log in. When prompted for a key type, choose a production key. The command generates an API key and project ID, then adds them to `.env.local` in your project root. The command is interactive; to set up without prompts, create the keys at [dash.generaltranslation.com](https://dash.generaltranslation.com) and add them to `.env.local` yourself: ```bash title=".env.local" GT_PROJECT_ID="your-project-id" From 07357bad253ad929f58ade3786941326e15973f1 Mon Sep 17 00:00:00 2001 From: JoshKappler Date: Tue, 21 Jul 2026 11:08:52 -0700 Subject: [PATCH 3/3] docs(pages-router): keep getServerSideProps in page samples and wire html lang Steps 7 and 8 replace pages/index.tsx without the wrapped getServerSideProps from step 5, and the contact.tsx sample contradicts the page FAQ, so strings silently stay untranslated. Show complete files, add the missing wrapper, restore the globals.css import in _app.tsx, name the --no-app scaffold flag, and add a FAQ entry that wires html lang through _document.tsx. --- .../react/nextjs-pages-router-quickstart.mdx | 60 +++++++++++++++++-- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/docs/en-US/react/nextjs-pages-router-quickstart.mdx b/docs/en-US/react/nextjs-pages-router-quickstart.mdx index f2a1e2ace..fb9881343 100644 --- a/docs/en-US/react/nextjs-pages-router-quickstart.mdx +++ b/docs/en-US/react/nextjs-pages-router-quickstart.mdx @@ -18,8 +18,8 @@ In the Pages Router, `gt-next` works through `getServerSideProps`: on each reque The `gt-next/server` entry is App Router-only and does not work with the Pages Router. **Prerequisites:** -- A Next.js app using the **Pages Router** -- Node.js 18+ +- A Next.js app using the **Pages Router**. To start fresh, run `npx create-next-app@latest my-app --no-app`. Without the `--no-app` flag, `create-next-app` scaffolds the App Router. +- Node.js 20.9+ (the floor the current Next.js release declares) **Note:** If you use the App Router, follow the [Next.js App Router Quickstart](/docs/react/nextjs-quickstart) instead. It uses server components and needs no `getServerSideProps` wiring. @@ -163,6 +163,7 @@ export const getServerSideProps = withGTServerSideProps(); The **`GTProvider`** component gives your entire app access to translations. In `_app.tsx`, pull the injected props out of `pageProps` and pass them to the provider. The **`WithGTServerSideProps`** type describes the injected shape: ```tsx title="pages/_app.tsx" +import '@/styles/globals.css'; import type { AppProps } from 'next/app'; import { GTProvider, WithGTServerSideProps } from 'gt-next'; @@ -185,10 +186,13 @@ Because the locale and translations arrive with the server response, the first r ### 7. Mark content for translation -Now, wrap any text you want translated with the **``** component. `` stands for "translate": +Now, wrap any text you want translated with the **``** component. `` stands for "translate". The block below shows the complete file; the wrapped `getServerSideProps` from Step 5 stays in place: ```tsx title="pages/index.tsx" -import { T } from 'gt-next'; +import type { GetServerSideProps } from 'next'; +import { T, withGTServerSideProps } from 'gt-next'; + +export const getServerSideProps: GetServerSideProps = withGTServerSideProps(); export default function Home() { return ( @@ -210,7 +214,10 @@ You can wrap as much or as little JSX as you want inside ``. Everything insid Drop in a **``** so users can change languages: ```tsx title="pages/index.tsx" -import { T, LocaleSelector } from 'gt-next'; +import type { GetServerSideProps } from 'next'; +import { T, LocaleSelector, withGTServerSideProps } from 'gt-next'; + +export const getServerSideProps: GetServerSideProps = withGTServerSideProps(); export default function Home() { return ( @@ -291,7 +298,9 @@ Open [http://localhost:3000](http://localhost:3000) and use the language dropdow For plain strings — like `placeholder` attributes, `aria-label` values, or `alt` text — use the **`useGT`** hook: ```tsx title="pages/contact.tsx" -import { useGT } from 'gt-next'; +import { useGT, withGTServerSideProps } from 'gt-next'; + +export const getServerSideProps = withGTServerSideProps(); export default function ContactPage() { const gt = useGT(); @@ -345,6 +354,45 @@ That's it — your app is now multilingual. 🎉 export const getServerSideProps = withGTServerSideProps(); ``` + + The Pages Router scaffold hardcodes `` in `pages/_document.tsx`, so the document language never follows the locale `withGTServerSideProps` resolves. Screen readers and search engines then see English on translated pages. Resolve the same locale in `_document.tsx`: + + ```tsx title="pages/_document.tsx" + import Document, { + Html, + Head, + Main, + NextScript, + DocumentContext, + DocumentInitialProps, + } from 'next/document'; + import { parseLocale } from 'gt-next'; + + type Props = DocumentInitialProps & { locale: string }; + + export default function MyDocument({ locale }: Props) { + return ( + + + +
+ + + + ); + } + + MyDocument.getInitialProps = async (ctx: DocumentContext): Promise => { + const initialProps = await Document.getInitialProps(ctx); + const locale = ctx.req ? parseLocale(ctx as any) : 'en'; + return { ...initialProps, locale }; + }; + ``` + + `parseLocale` is typed for `getServerSideProps` contexts but only reads `req`, which `DocumentContext` also provides, hence the cast. It applies the same cookie-then-`Accept-Language` resolution as `withGTServerSideProps`, so the `lang` attribute and the rendered content stay consistent. + + This applies to server-rendered pages. Statically generated pages (`withGTStaticProps`) render at build time, when there is no user request to read a cookie or `Accept-Language` header from, so `parseLocale` returns the default and the `lang` attribute stays the fallback. + Yes. Wrap the page with `withGTStaticProps` and keep passing the generated props to `GTProvider` in `_app.tsx`. See the [Pages Router static site generation guide](/docs/react/nextjs/pages-router-static-site-generation) for the complete setup.