Skip to content

Commit 8c8edf1

Browse files
authored
Release/1.0.0 (#8)
* refactor SSR implementation
1 parent a0f7ded commit 8c8edf1

File tree

11 files changed

+874
-481
lines changed

11 files changed

+874
-481
lines changed

README.md

Lines changed: 152 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ import { TranslationProvider } from "react-autolocalise";
3838
const App = () => {
3939
const config = {
4040
apiKey: "your-api-key",
41-
sourceLocale: "fr",
42-
targetLocale: "en",
41+
sourceLocale: "en", // Your app's original language
42+
targetLocale: "es", // Language to translate to
4343
// cacheTTL: 24, // Cache validity in hours (optional, defaults to 24)
4444
};
4545

@@ -117,139 +117,166 @@ const MyComponent = () => {
117117

118118
## Next.js Server-Side Rendering Support
119119

120-
This SDK provides comprehensive SSR support through middleware-based locale detection and server components. Here's how to implement end-to-end server-side translation:
120+
This SDK provides reliable SSR support with automatic locale detection and server-side translation. The new approach is simple, predictable, and SEO-friendly.
121121

122-
### Middleware Setup for language detection
122+
### Middleware Setup for Dynamic Locale Detection
123123

124-
Create a middleware file to detect user's locale from request headers or URL parameters:
124+
Create a middleware file to detect user's locale and set up dynamic routing:
125125

126-
```tsx:/src/middleware.ts
126+
```tsx
127+
// src/middleware.ts
127128
import { NextResponse } from "next/server";
128129
import type { NextRequest } from "next/server";
129130

130-
const defaultLocale = "en";
131-
132131
export function middleware(request: NextRequest) {
133-
const { searchParams } = new URL(request.url);
134-
const localeParam = searchParams.get("locale");
135-
132+
const { pathname } = request.nextUrl;
133+
134+
// Skip middleware for API routes, static files, and Next.js internals or anything you want
135+
if (
136+
pathname.startsWith("/api") ||
137+
pathname.startsWith("/_next") ||
138+
pathname.includes(".")
139+
) {
140+
return NextResponse.next();
141+
}
142+
143+
// Get locale from accept-language header
136144
const acceptLanguage = request.headers.get("accept-language");
137-
const browserLocale = acceptLanguage?.split(',')[0].split(';')[0].substring(0,2);
145+
const browserLocale = acceptLanguage?.split(",")[0]?.split("-")[0] || "en";
146+
147+
// Support any locale dynamically, you can also predefine a list here to control the target languages
148+
const locale = browserLocale;
149+
150+
// Redirect root to locale-specific URL
151+
if (pathname === "/") {
152+
return NextResponse.redirect(new URL(`/${locale}`, request.url));
153+
}
138154

139-
const locale = localeParam || browserLocale || defaultLocale;
155+
// If path doesn't start with a locale, redirect to add locale
156+
const pathSegments = pathname.split("/");
157+
const firstSegment = pathSegments[1];
140158

141-
const response = NextResponse.next();
142-
response.headers.set("x-locale", locale);
143-
return response;
159+
// Simple check: if first segment is not a 2-letter code, add locale
160+
if (!firstSegment || firstSegment.length !== 2) {
161+
return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
162+
}
163+
164+
return NextResponse.next();
144165
}
145166

146167
export const config = {
147-
matcher: "/:path*",
168+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
148169
};
149170
```
150171

151-
### Initialize Translation Service (Singleton Pattern)
172+
### Server Component Implementation
152173

153-
The SDK uses a singleton pattern for the TranslationService to ensure efficient caching and batch processing. Create a utility file to manage the translator instance:
174+
The `withServerTranslation` HOC provides the cleanest server-side translation experience with zero string duplication. Here are the most common use cases:
154175

155-
```typescript
156-
// utils/translator.ts
157-
import ServerTranslation from "react-autolocalise/server";
176+
#### **Dynamic Locale from URL**
177+
178+
```tsx
179+
// app/[locale]/page.tsx
180+
import { withServerTranslation } from "react-autolocalise/server";
158181

159182
const config = {
160183
apiKey: "your-api-key",
161-
sourceLocale: "fr",
162-
targetLocale: "en",
184+
sourceLocale: "en", // Your app's original language
163185
};
164186

165-
// Simple function to get a translator instance
166-
export function getTranslator() {
167-
return new ServerTranslation(config);
187+
// Clean HOC approach - automatically uses locale from props
188+
const HomePage = withServerTranslation(config, ({ t, tf, locale }) => (
189+
<div>
190+
<h1>{t("Welcome to our app")}</h1>
191+
<p>{t("This content is automatically translated")}</p>
192+
{tf(
193+
<>
194+
Experience <strong>powerful</strong> and <em>reliable</em> translations!
195+
</>
196+
)}
197+
<p>Current language: {locale}</p>
198+
</div>
199+
));
200+
201+
export default async function Page({
202+
params,
203+
}: {
204+
params: Promise<{ locale: string }>;
205+
}) {
206+
const { locale } = await params;
207+
return <HomePage locale={locale} />;
168208
}
169209
```
170210

171-
### Server Component Implementation
172-
173-
Create server components that utilize the detected locale:
174-
175-
> **Note**: For server-side rendering, all translations must be completed before sending the response to the client. This requires a two-step process: first mark texts for translation using t() , then execute all translations in a single batch with execute() . This ensures all translations are ready before rendering occurs.
176-
177-
**Basic Usage:**
211+
#### **Fixed Target Language**
178212

179213
```tsx
180-
import { getTranslator } from "@/utils/translator";
181-
182-
async function ServerComponent() {
183-
const translator = getTranslator();
184-
185-
// Mark texts for translation
186-
const title = translator.t("Hello from Server Component");
187-
const description = translator.t(
188-
"This component is rendered on the server side"
189-
);
190-
191-
// Execute all translations in a single batch
192-
await translator.execute();
193-
194-
// Get translated texts
195-
return (
196-
<div>
197-
<h1>{translator.get(title)}</h1>
198-
<p>{translator.get(description)}</p>
199-
</div>
200-
);
201-
}
214+
// For apps targeting a specific language (e.g., Spanish market)
215+
const config = {
216+
apiKey: "your-api-key",
217+
sourceLocale: "en",
218+
targetLocale: "es", // Always translate to Spanish
219+
};
202220

203-
export default ServerComponent;
221+
const Page = withServerTranslation(config, ({ t, tf }) => (
222+
<div>
223+
<h1>{t("Welcome to our app")}</h1>
224+
<p>{t("All content will be in Spanish")}</p>
225+
{tf(
226+
<>
227+
Built for <strong>Spanish</strong> speaking users!
228+
</>
229+
)}
230+
</div>
231+
));
232+
233+
export default Page;
204234
```
205235

206-
**Use with nested text formatting:**
207-
208-
For components with styled text, use `tFormatted()` and `getFormatted()` to preserve formatting:
236+
### SEO Benefits
209237

210-
```tsx
211-
import { getTranslator } from "@/utils/translator";
238+
The server-side rendering approach provides excellent SEO benefits:
212239

213-
async function FormattedServerComponent() {
214-
const translator = getTranslator();
240+
- **Translated content in HTML**: Search engines see fully translated content on first load
241+
- **Locale-specific URLs**: Clean URLs like `/zh/about`, `/fr/contact` for better indexing
242+
- **Dynamic locale support**: Automatically handles any language without pre-configuration
243+
- **Fast server-side translation**: Efficient caching reduces API calls and improves performance
215244

216-
// Mark formatted text with nested styling for translation
217-
const formattedContent = (
218-
<>
219-
Hello, we <span style={{ color: "red" }}>want</span> you to be{" "}
220-
<strong style={{ fontWeight: "bold" }}>happy</strong>!
221-
</>
222-
);
223-
// Mark the formatted texts for translation
224-
translator.tFormatted(formattedContent);
245+
**Generating SEO Metadata:**
225246

226-
// Also mark some regular text
227-
const subtitle = translator.t("Server-side nested formatting example");
247+
```tsx
248+
// app/[locale]/layout.tsx
249+
import { translateServerStrings } from "react-autolocalise/server";
228250

229-
// Execute all translations in a single batch
230-
await translator.execute();
251+
const config = {
252+
apiKey: "your-api-key",
253+
sourceLocale: "en",
254+
};
231255

232-
return (
233-
<div>
234-
<h3>{translator.get(subtitle)}</h3>
235-
<p>{translator.getFormatted(formattedContent)}</p>
236-
</div>
237-
);
256+
export async function generateMetadata({
257+
params,
258+
}: {
259+
params: Promise<{ locale: string }>;
260+
}) {
261+
const { locale } = await params;
262+
263+
const strings = [
264+
"My Awesome App - Best Solution for Your Business",
265+
"Discover the most powerful tools to grow your business online",
266+
];
267+
268+
const translations = await translateServerStrings(strings, locale, config);
269+
270+
return {
271+
title: translations["My Awesome App - Best Solution for Your Business"],
272+
description:
273+
translations[
274+
"Discover the most powerful tools to grow your business online"
275+
],
276+
};
238277
}
239-
240-
export default FormattedServerComponent;
241278
```
242279

243-
### SEO Considerations
244-
245-
While our SDK currently supports server-side rendering of translated content, achieving full locale-specific visibility in search engine results requires additional implementation. We're working on this step by step example and welcome community contributions to:
246-
247-
- Implement canonical URL handling for localized content
248-
- Develop locale-specific sitemap generation
249-
- Show hreflang tag implementation
250-
251-
If you'd like to contribute examples or implementations for these features, please submit a Pull Request!
252-
253280
## Locale Format
254281

255282
The locale format follows the ISO 639-1 language code standard, optionally combined with an ISO 3166-1 country code:
@@ -274,37 +301,55 @@ const languageCode = browserLocale.split("-")[0]; // e.g., 'en'
274301

275302
## API Reference
276303

277-
### TranslationProvider Props
304+
### Client-Side API
305+
306+
#### TranslationProvider Props
278307

279308
| Prop | Type | Description |
280309
| ------ | ----------------- | ------------------------------------------------ |
281310
| config | TranslationConfig | Configuration object for the translation service |
282311

283-
### TranslationConfig
312+
#### useAutoTranslate Hook
284313

285-
| Property | Type | Required | Description |
286-
| ------------ | ------ | -------- | -------------------------------------------- |
287-
| apiKey | string | Yes | Your API key for the translation service |
288-
| sourceLocale | string | Yes | Source locale for translations |
289-
| targetLocale | string | Yes | Target locale for translations |
290-
| cacheTTL | number | No | Cache validity period in hours (default: 24) |
314+
Returns an object with:
291315

292-
**Tips**: When `sourceLocale` === `targetLocale` no translation requests will be send.
316+
- `t`: Translation function
317+
- `loading`: Boolean indicating initialization of translations
318+
- `error`: Error object if translation loading failed
293319

294-
### useAutoTranslate Hook
320+
## API Reference
321+
322+
### Client-Side API
323+
324+
#### TranslationProvider Props
325+
326+
| Prop | Type | Description |
327+
| ------ | ----------------- | ------------------------------------------------ |
328+
| config | TranslationConfig | Configuration object for the translation service |
329+
330+
#### useAutoTranslate Hook
295331

296332
Returns an object with:
297333

298334
- `t`: Translation function
299335
- `loading`: Boolean indicating initialization of translations
300336
- `error`: Error object if translation loading failed
301337

338+
### TranslationConfig
339+
340+
| Property | Type | Required | Description |
341+
| ------------ | ------ | -------- | -------------------------------------------- |
342+
| apiKey | string | Yes | Your API key for the translation service |
343+
| sourceLocale | string | Yes | Source locale for translations |
344+
| targetLocale | string | Yes | Target locale |
345+
| cacheTTL | number | No | Cache validity period in hours (default: 24) |
346+
347+
**Tips**: When `sourceLocale` === `targetLocale` no translation requests will be sent.
348+
302349
### Persist for Editing
303350

304351
The 'persist' means the string will be persisted so that you can review and edit in the [dashboard](https://dashboard.autolocalise.com), default is true, if the content is dynamic or you don't want to see in the dashboard, pass 'false'.
305352

306-
**Note**: Server-side rendering only works with persist = true by default.
307-
308353
```typescript
309354
import { useAutoTranslate } from "react-autolocalise";
310355
const MyComponent = () => {

0 commit comments

Comments
 (0)