You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The type import and the `satisfies` are optional, but it will help us ensure that if we add or remove a key from the `en` locale (our default one) we will get a type error in the `es` locale so we can keep them in sync.
70
+
The `satisfies typeof import("~/locales/en/translation").default` is optional, but it will help to ensure that if we add or remove a key from the `en` locale (the default one) we will get a type error in the `es` locale so we can keep them in sync.
71
+
72
+
Then re-export them all the locales in `app/locales/index.ts`:
73
+
74
+
```ts
75
+
importtype { Resource } from"i18next";
76
+
importenfrom"./en";
77
+
importesfrom"./es";
78
+
79
+
exportdefault { en, es } satisfiesResource;
80
+
```
61
81
62
82
### Setup the Middleware
63
83
@@ -71,21 +91,38 @@ Create a file named `app/middleware/i18next.ts` with the following code:
71
91
> Check older versions of the README for a guide on how to use RemixI18next class instead if you are using an older version of React Router or don't want to use the middleware.
resources: { en: { translation: en }, es: { translation: es } },
86
-
// Other i18next options are available here
111
+
supportedLanguages: ["es", "en"], // Your supported languages, the fallback should be last
112
+
fallbackLanguage: "en", // Your fallback language
113
+
cookie: localeCookie, // The cookie to store the user preference
87
114
},
115
+
i18next: { resources }, // Your locales
116
+
plugins: [initReactI18next], // Plugins you may need, like react-i18next
88
117
});
118
+
119
+
// This adds type-safety to the `t` function
120
+
declaremodule"i18next" {
121
+
interfaceCustomTypeOptions {
122
+
defaultNS:"translation";
123
+
resources:typeofresources.en; // Use `en` as source of truth for the types
124
+
}
125
+
}
89
126
```
90
127
91
128
Then in your `app/root.tsx` setup the middleware:
@@ -151,7 +188,7 @@ This will return a new `TFunction` instance with the locale set to `es`.
151
188
152
189
### Usage with react-i18next
153
190
154
-
So far this has configured the i18next instance to inside React Router loaders and actions, but in many cases we will need to use it directly in our React components.
191
+
So far this has configured the i18next instance to use inside React Router loaders and actions, but in many cases we will need to use it directly in our React components.
155
192
156
193
To do this, we need to setup react-i18next.
157
194
@@ -160,7 +197,7 @@ Let's start by updating the `entry.client.tsx` and `entry.server.tsx` files to u
160
197
> [!TIP]
161
198
> If you don't have these files, run `npx react-router reveal` to generate them. They are hidden by default.
162
199
163
-
### Update the root route
200
+
####Update the root route
164
201
165
202
First of all, we want to send the locale detected server-side by the middleware to the UI. To do this, we will return the locale from the `app/root.tsx` route.
if (i18n.language!==locale) i18n.changeLanguage(locale);
256
+
}, [locale, i18n]);
217
257
return <Outlet />;
218
258
}
219
259
```
220
260
221
261
We made a few changes here:
222
262
223
-
1. We added a `loader` that gets the locale from the context (set by the middleware) and returns it to the UI.
263
+
1. We added a `loader` that gets the locale from the context (set by the middleware) and returns it to the UI. It also saves the locale in a cookie so it can be read from there in future requests.
224
264
2. In the root Layout component, we use the `useTranslation` hook to get the i18n instance and set the `lang` attribute of the `<html>` tag, along the `dir` attribute.
225
-
3. We added the `useChangeLanguage` hook to set the language in the i18next instance, this will keep the language in sync with the locale detected by the middleware after a refresh of the loader data.
265
+
3. We added a `useEffect` in the `App` component to change the language of the i18n instance when the locale changes. This keeps the i18n instance in sync with the locale detected server-side.
226
266
227
267
#### Client-side configuration
228
268
@@ -243,8 +283,11 @@ async function main() {
243
283
.use(Fetch)
244
284
.use(I18nextBrowserLanguageDetector)
245
285
.init({
246
-
fallbackLng: "en",
286
+
fallbackLng: "en", // Change this to your default language
287
+
// Here we only want to detect the language from the html tag
288
+
// since the middleware already detected the language server-side
247
289
detection: { order: ["htmlTag"], caches: [] },
290
+
// Update this to the path where your locales will be served
We're configuring `i18next-browser-languagedetector` to detect the language based on the `lang` attribute of the `<html>` tag. This way, we can use the same language detected by the middleware server-side.
267
310
268
-
### API for locales
311
+
####API for locales
269
312
270
313
The `app/entry.client.tsx` has the i18next backend configured to load the locales from the path `/api/locales/{{lng}}/{{ns}}`. Feel free to customize this but for this guide we will use that path.
271
314
@@ -275,32 +318,20 @@ Now we need to create a route to serve the locales. So let's create a file `app/
// Serve stale content if there's an error for 7 days
320
351
staleIfError: "7d",
321
-
})
352
+
}),
322
353
);
323
354
}
324
355
@@ -335,17 +366,13 @@ They are not hard requirements, but they are useful for our example, feel free t
335
366
336
367
Finally, ensure the route is configured in your `app/routes.ts` file. You can set the path to `/api/locales/:lng/:ns` so it matches the path used in the `entry.client.tsx` file, if you use something else remember to update the `loadPath` in the i18next configuration.
337
368
338
-
### Server-side configuration
369
+
####Server-side configuration
339
370
340
371
Now in your `entry.server.tsx` replace the default code with this:
@@ -361,7 +388,7 @@ export default function handleRequest(
361
388
responseStatusCode:number,
362
389
responseHeaders:Headers,
363
390
entryContext:EntryContext,
364
-
routerContext:RouterContextProvider
391
+
routerContext:RouterContextProvider,
365
392
) {
366
393
returnnewPromise((resolve, reject) => {
367
394
let shellRendered =false;
@@ -388,7 +415,7 @@ export default function handleRequest(
388
415
newResponse(stream, {
389
416
headers: responseHeaders,
390
417
status: responseStatusCode,
391
-
})
418
+
}),
392
419
);
393
420
394
421
pipe(body);
@@ -400,7 +427,7 @@ export default function handleRequest(
400
427
responseStatusCode=500;
401
428
if (shellRendered) console.error(error);
402
429
},
403
-
}
430
+
},
404
431
);
405
432
406
433
setTimeout(abort, streamTimeout+1000);
@@ -412,7 +439,9 @@ Here we are using the `getInstance` function from the middleware to get the i18n
412
439
413
440
This way, we can re-use the instance created in the middleware and avoid creating a new one. And since the instance is already configured with the language we detected, we can use it directly in the `I18nextProvider`.
414
441
415
-
## Finding the locale from the request URL pathname
442
+
## Common Scenarios
443
+
444
+
### Finding the locale from the request URL pathname
416
445
417
446
If you want to keep the user locale on the pathname, you have two possible options.
The locale returned by `findLocale` will be validated against the list of supported locales, in case it's not valid the fallback locale will be used.
443
472
444
-
## Querying the locale from the database
473
+
###Querying the locale from the database
445
474
446
475
If your application stores the user locale in the database, you can use `findLocale` function to query the database and return the locale.
447
476
@@ -458,7 +487,7 @@ export let i18n = new RemixI18Next({
458
487
});
459
488
```
460
489
461
-
## Store the locale in a cookie
490
+
###Store the locale in a cookie
462
491
463
492
If you want to store the locale in a cookie, you can create a cookie using `createCookie` helper from React Router and pass the Cookie object to the middleware.
Similarly to the cookie, you can store the locale in the session. To do this, you can create a session using `createSessionStorage` helpers from React Router and pass the SessionStorage object to the middleware.
If you want to handle not found errors and show a custom internationalized 404 page, you can create a route with the path `*` and render your custom 404 page there.
604
+
605
+
```tsx
606
+
import { useTranslation } from"react-i18next";
607
+
import { data, href, Link } from"react-router";
608
+
609
+
exportasyncfunction loader() {
610
+
returndata(null, { status: 404 }); // Set the status to 404
If you're using `@react-router/fs-routes` package, you can create a file named `app/routes/$.tsx` and it will be used automatically. If you're configuring your routes manually, create a file `app/routes/not-found.tsx` and add `route("*", "./routes/not-found.tsx")` to your routes configuration.
628
+
629
+
Without this route, any not found request will not run the middleware in root, causing `entry.server.tsx` to not have the i18next instance configured, resulting in an error.
0 commit comments