Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 54 additions & 6 deletions docs/en-US/react/nextjs-pages-router-quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<Callout type='info'>
**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.
Expand Down Expand Up @@ -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';

Expand All @@ -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 **`<T>`** component. `<T>` stands for "translate":
Now, wrap any text you want translated with the **`<T>`** component. `<T>` 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 (
Expand All @@ -210,7 +214,10 @@ You can wrap as much or as little JSX as you want inside `<T>`. Everything insid
Drop in a **`<LocaleSelector>`** 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 (
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -345,6 +354,45 @@ That's it — your app is now multilingual. 🎉
export const getServerSideProps = withGTServerSideProps();
```
</Accordion>
<Accordion title="Why doesn't <html lang> follow the locale?">
The Pages Router scaffold hardcodes `<Html lang="en">` 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 (
<Html lang={locale}>
<Head />
<body className="antialiased">
<Main />
<NextScript />
</body>
</Html>
);
}

MyDocument.getInitialProps = async (ctx: DocumentContext): Promise<Props> => {
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.
</Accordion>
<Accordion title="Can I use getStaticProps instead?">
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.
</Accordion>
Expand Down
96 changes: 78 additions & 18 deletions docs/en-US/react/react-quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Callout type='info'>
**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.
Expand Down Expand Up @@ -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"
}
}
}
Expand All @@ -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`);
Expand All @@ -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,
Expand All @@ -112,40 +114,82 @@ 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 **`<GTProvider>`**:
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 **`<GTProvider>`**. 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,
translations: await getTranslationsSnapshot(locale), // [!code highlight]
};
}

export default function Root({ children }) {
const { locale, translations } = useLoaderData();
export function Layout({ children }: { children: React.ReactNode }) {
const locale =
useRouteLoaderData<typeof loader>('root')?.locale ?? gtConfig.defaultLocale;
return (
<html lang={locale}>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

export default function App() {
const { locale, translations } = useLoaderData<typeof loader>();
return (
<GTProvider locale={locale} translations={translations}>
{children}
<Outlet />
</GTProvider>
);
}
```

The `Layout` change makes `<html lang>` 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 **`<T>`** component. `<T>` stands for "translate":

```tsx title="src/components/Welcome.tsx"
```tsx title="app/components/Welcome.tsx"
import { T } from 'gt-react';

export default function Welcome() {
Expand All @@ -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() {
Expand All @@ -176,7 +220,7 @@ export default function ContactForm() {

Drop in a **`<LocaleSelector>`** 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() {
Expand All @@ -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 (
<>
<Header />
<Welcome />
</>
);
}
```


### 8. Set up environment variables (optional)

Expand Down
19 changes: 16 additions & 3 deletions docs/en-US/react/react-spa-quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<Callout type='info'>
**Tip:** Run `npx gt@latest` to configure everything with the [Setup Wizard](/docs/cli/reference/commands/init). This guide covers manual setup.
Expand Down Expand Up @@ -165,9 +165,12 @@ start().catch(console.error);
</Accordion>
</Accordions>

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(
Expand Down Expand Up @@ -214,6 +217,16 @@ export default function Welcome() {

You can wrap as much or as little JSX as you want inside `<T>`. 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 <Welcome />;
}
```

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"
Expand Down Expand Up @@ -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"
Expand Down
Loading