@intlayer/docs 7.0.0-canary.1 → 7.0.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common.cjs.map +1 -1
- package/dist/esm/common.mjs.map +1 -1
- package/dist/types/common.d.ts +5 -0
- package/dist/types/common.d.ts.map +1 -1
- package/docs/ar/intlayer_with_nextjs_16.md +1652 -0
- package/docs/ar/releases/v7.md +486 -0
- package/docs/de/intlayer_with_nextjs_16.md +1662 -0
- package/docs/de/releases/v7.md +503 -0
- package/docs/en/intlayer_with_nextjs_15.md +5 -2
- package/docs/en/intlayer_with_nextjs_16.md +4 -4
- package/docs/en-GB/intlayer_with_nextjs_16.md +1642 -0
- package/docs/en-GB/releases/v7.md +486 -0
- package/docs/es/intlayer_with_nextjs_16.md +1670 -0
- package/docs/es/releases/v7.md +503 -0
- package/docs/fr/intlayer_with_nextjs_16.md +1692 -0
- package/docs/fr/releases/v7.md +504 -0
- package/docs/hi/intlayer_with_nextjs_16.md +1618 -0
- package/docs/hi/releases/v7.md +486 -0
- package/docs/id/intlayer_with_nextjs_16.md +1604 -0
- package/docs/id/releases/v7.md +503 -0
- package/docs/it/intlayer_with_nextjs_16.md +1600 -0
- package/docs/it/releases/v7.md +505 -0
- package/docs/ja/intlayer_CMS.md +0 -9
- package/docs/ja/intlayer_with_nextjs_16.md +1788 -0
- package/docs/ja/releases/v7.md +504 -0
- package/docs/ko/intlayer_with_nextjs_16.md +1641 -0
- package/docs/ko/releases/v7.md +504 -0
- package/docs/pl/intlayer_with_nextjs_16.md +1645 -0
- package/docs/pl/releases/v7.md +486 -0
- package/docs/pt/intlayer_with_nextjs_16.md +1646 -0
- package/docs/pt/introduction.md +0 -15
- package/docs/pt/releases/v7.md +486 -0
- package/docs/ru/intlayer_with_nextjs_16.md +1610 -0
- package/docs/ru/releases/v7.md +486 -0
- package/docs/tr/intlayer_with_nextjs_16.md +1599 -0
- package/docs/tr/releases/v7.md +486 -0
- package/docs/vi/intlayer_with_nextjs_16.md +1597 -0
- package/docs/vi/releases/v7.md +486 -0
- package/docs/zh/intlayer_CMS.md +0 -23
- package/docs/zh/intlayer_with_nextjs_16.md +1628 -0
- package/docs/zh/releases/v7.md +487 -0
- package/package.json +14 -14
- package/src/common.ts +5 -0
|
@@ -0,0 +1,1646 @@
|
|
|
1
|
+
---
|
|
2
|
+
createdAt: 2025-10-25
|
|
3
|
+
updatedAt: 2025-10-25
|
|
4
|
+
title: Como traduzir sua aplicação Next.js 16 – guia i18n 2025
|
|
5
|
+
description: Descubra como tornar seu site Next.js 16 multilíngue. Siga a documentação para internacionalizar (i18n) e traduzir.
|
|
6
|
+
keywords:
|
|
7
|
+
- Internacionalização
|
|
8
|
+
- Documentação
|
|
9
|
+
- Intlayer
|
|
10
|
+
- Next.js 16
|
|
11
|
+
- JavaScript
|
|
12
|
+
- React
|
|
13
|
+
slugs:
|
|
14
|
+
- doc
|
|
15
|
+
- environment
|
|
16
|
+
- nextjs
|
|
17
|
+
applicationTemplate: https://github.com/aymericzip/intlayer-next-16-template
|
|
18
|
+
youtubeVideo: https://www.youtube.com/watch?v=e_PPG7PTqGU
|
|
19
|
+
history:
|
|
20
|
+
- version: 7.0.0
|
|
21
|
+
date: 2025-06-29
|
|
22
|
+
changes: Histórico inicial
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Traduza seu site Next.js 16 usando Intlayer | Internacionalização (i18n)
|
|
26
|
+
|
|
27
|
+
<iframe title="A melhor solução i18n para Next.js? Descubra o Intlayer" class="m-auto aspect-[16/9] w-full overflow-hidden rounded-lg border-0" allow="autoplay; gyroscope;" loading="lazy" width="1080" height="auto" src="https://www.youtube.com/embed/e_PPG7PTqGU?autoplay=0&origin=http://intlayer.org&controls=0&rel=1"/>
|
|
28
|
+
|
|
29
|
+
Veja o [Modelo de Aplicação](https://github.com/aymericzip/intlayer-next-16-template) no GitHub.
|
|
30
|
+
|
|
31
|
+
## O que é o Intlayer?
|
|
32
|
+
|
|
33
|
+
**Intlayer** é uma biblioteca inovadora e de código aberto para internacionalização (i18n) projetada para simplificar o suporte multilíngue em aplicações web modernas. O Intlayer integra-se perfeitamente com o mais recente framework **Next.js 16**, incluindo seu poderoso **App Router**. É otimizado para funcionar com **Server Components** para renderização eficiente e é totalmente compatível com [**Turbopack**](https://nextjs.org/docs/architecture/turbopack).
|
|
34
|
+
|
|
35
|
+
Com o Intlayer, você pode:
|
|
36
|
+
|
|
37
|
+
- **Gerenciar traduções facilmente** usando dicionários declarativos no nível do componente.
|
|
38
|
+
- **Localizar dinamicamente metadados**, rotas e conteúdo.
|
|
39
|
+
- **Acessar traduções tanto em componentes do lado do cliente quanto do lado do servidor**.
|
|
40
|
+
- **Garantir suporte ao TypeScript** com tipos gerados automaticamente, melhorando a autocompletação e a detecção de erros.
|
|
41
|
+
- **Beneficie de recursos avançados**, como detecção e troca dinâmica de localidade.
|
|
42
|
+
|
|
43
|
+
> O Intlayer é compatível com Next.js 12, 13, 14 e 16. Se você estiver usando o Next.js Page Router, pode consultar este [guia](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/intlayer_with_nextjs_page_router.md). Para Next.js 12, 13, 14 com App Router, consulte este [guia](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/intlayer_with_nextjs_14.md).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Guia passo a passo para configurar o Intlayer em uma aplicação Next.js
|
|
48
|
+
|
|
49
|
+
### Passo 1: Instalar dependências
|
|
50
|
+
|
|
51
|
+
Instale os pacotes necessários usando npm:
|
|
52
|
+
|
|
53
|
+
```bash packageManager="npm"
|
|
54
|
+
npm install intlayer next-intlayer
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash packageManager="pnpm"
|
|
58
|
+
pnpm add intlayer next-intlayer
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash packageManager="yarn"
|
|
62
|
+
yarn add intlayer next-intlayer
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **intlayer**
|
|
66
|
+
|
|
67
|
+
O pacote principal que fornece ferramentas de internacionalização para gerenciamento de configuração, tradução, [declaração de conteúdo](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/dictionary/content_file.md), transpiração e [comandos CLI](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/intlayer_cli.md).
|
|
68
|
+
|
|
69
|
+
- **next-intlayer**
|
|
70
|
+
|
|
71
|
+
O pacote que integra o Intlayer com Next.js. Ele fornece provedores de contexto e hooks para internacionalização no Next.js. Além disso, inclui o plugin do Next.js para integrar o Intlayer com [Webpack](https://webpack.js.org/) ou [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack), assim como um proxy para detectar o idioma preferido do usuário, gerenciar cookies e lidar com redirecionamento de URL.
|
|
72
|
+
|
|
73
|
+
### Passo 2: Configure Seu Projeto
|
|
74
|
+
|
|
75
|
+
Crie um arquivo de configuração para configurar os idiomas da sua aplicação:
|
|
76
|
+
|
|
77
|
+
```typescript fileName="intlayer.config.ts" codeFormat="typescript"
|
|
78
|
+
import { Locales, type IntlayerConfig } from "intlayer";
|
|
79
|
+
|
|
80
|
+
const config: IntlayerConfig = {
|
|
81
|
+
internationalization: {
|
|
82
|
+
locales: [
|
|
83
|
+
Locales.ENGLISH,
|
|
84
|
+
Locales.FRENCH,
|
|
85
|
+
Locales.SPANISH,
|
|
86
|
+
// Seus outros idiomas
|
|
87
|
+
],
|
|
88
|
+
defaultLocale: Locales.ENGLISH,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default config;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```javascript fileName="intlayer.config.mjs" codeFormat="esm"
|
|
96
|
+
import { Locales } from "intlayer";
|
|
97
|
+
|
|
98
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
99
|
+
const config = {
|
|
100
|
+
internationalization: {
|
|
101
|
+
locales: [
|
|
102
|
+
Locales.ENGLISH,
|
|
103
|
+
Locales.FRENCH,
|
|
104
|
+
Locales.SPANISH,
|
|
105
|
+
// Seus outros idiomas
|
|
106
|
+
],
|
|
107
|
+
defaultLocale: Locales.ENGLISH,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default config;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```javascript fileName="intlayer.config.cjs" codeFormat="commonjs"
|
|
115
|
+
const { Locales } = require("intlayer");
|
|
116
|
+
|
|
117
|
+
/** @type {import('intlayer').IntlayerConfig} */
|
|
118
|
+
const config = {
|
|
119
|
+
internationalization: {
|
|
120
|
+
locales: [
|
|
121
|
+
Locales.ENGLISH,
|
|
122
|
+
Locales.FRENCH,
|
|
123
|
+
Locales.SPANISH,
|
|
124
|
+
// Seus outros idiomas
|
|
125
|
+
],
|
|
126
|
+
defaultLocale: Locales.ENGLISH,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
module.exports = config;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> Através deste arquivo de configuração, você pode configurar URLs localizadas, redirecionamento de proxy, nomes de cookies, a localização e extensão das suas declarações de conteúdo, desabilitar logs do Intlayer no console, e muito mais. Para uma lista completa dos parâmetros disponíveis, consulte a [documentação de configuração](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/configuration.md).
|
|
134
|
+
|
|
135
|
+
### Passo 3: Integre o Intlayer na sua Configuração Next.js
|
|
136
|
+
|
|
137
|
+
Configure seu setup Next.js para usar o Intlayer:
|
|
138
|
+
|
|
139
|
+
```typescript fileName="next.config.ts" codeFormat="typescript"
|
|
140
|
+
import type { NextConfig } from "next";
|
|
141
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
142
|
+
|
|
143
|
+
const nextConfig: NextConfig = {
|
|
144
|
+
/* opções de configuração aqui */
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default withIntlayer(nextConfig);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```typescript fileName="next.config.mjs" codeFormat="esm"
|
|
151
|
+
import { withIntlayer } from "next-intlayer/server";
|
|
152
|
+
|
|
153
|
+
/** @type {import('next').NextConfig} */
|
|
154
|
+
const nextConfig = {
|
|
155
|
+
/* opções de configuração aqui */
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default withIntlayer(nextConfig);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```typescript fileName="next.config.cjs" codeFormat="commonjs"
|
|
162
|
+
const { withIntlayer } = require("next-intlayer/server");
|
|
163
|
+
|
|
164
|
+
/** @type {import('next').NextConfig} */
|
|
165
|
+
const nextConfig = {
|
|
166
|
+
/* opções de configuração aqui */
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
module.exports = withIntlayer(nextConfig);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> O plugin `withIntlayer()` do Next.js é usado para integrar o Intlayer com o Next.js. Ele garante a construção dos arquivos de declaração de conteúdo e os monitora no modo de desenvolvimento. Define variáveis de ambiente do Intlayer dentro dos ambientes [Webpack](https://webpack.js.org/) ou [Turbopack](https://nextjs.org/docs/app/api-reference/turbopack). Além disso, fornece aliases para otimizar o desempenho e garante compatibilidade com componentes do servidor.
|
|
173
|
+
|
|
174
|
+
> A função `withIntlayer()` é uma função do tipo promise. Ela permite preparar os dicionários do Intlayer antes do início da build. Se você quiser usá-la com outros plugins, pode usar await. Exemplo:
|
|
175
|
+
>
|
|
176
|
+
> ```tsx
|
|
177
|
+
> const nextConfig = await withIntlayer(nextConfig);
|
|
178
|
+
> const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
|
|
179
|
+
>
|
|
180
|
+
> export default nextConfigWithOtherPlugins;
|
|
181
|
+
> ```
|
|
182
|
+
>
|
|
183
|
+
> Se quiser usá-lo de forma síncrona, pode usar a função `withIntlayerSync()`. Exemplo:
|
|
184
|
+
>
|
|
185
|
+
> ```tsx
|
|
186
|
+
> const nextConfig = withIntlayerSync(nextConfig);
|
|
187
|
+
> const nextConfigWithOtherPlugins = withOtherPlugins(nextConfig);
|
|
188
|
+
>
|
|
189
|
+
> export default nextConfigWithOtherPlugins;
|
|
190
|
+
> ```
|
|
191
|
+
|
|
192
|
+
### Passo 4: Definir Rotas Dinâmicas de Locale
|
|
193
|
+
|
|
194
|
+
Remova tudo de `RootLayout` e substitua pelo seguinte código:
|
|
195
|
+
|
|
196
|
+
```tsx {3} fileName="src/app/layout.tsx" codeFormat="typescript"
|
|
197
|
+
import type { PropsWithChildren, FC } from "react";
|
|
198
|
+
import "./globals.css";
|
|
199
|
+
|
|
200
|
+
const RootLayout: FC<PropsWithChildren> = ({ children }) => (
|
|
201
|
+
// Você ainda pode envolver os filhos com outros providers, como `next-themes`, `react-query`, `framer-motion`, etc.
|
|
202
|
+
<>{children}</>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
export default RootLayout;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```jsx {3} fileName="src/app/layout.mjx" codeFormat="esm"
|
|
209
|
+
import "./globals.css";
|
|
210
|
+
|
|
211
|
+
const RootLayout = ({ children }) => (
|
|
212
|
+
// Você ainda pode envolver os filhos com outros provedores, como `next-themes`, `react-query`, `framer-motion`, etc.
|
|
213
|
+
<>{children}</>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
export default RootLayout;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```jsx {1,8} fileName="src/app/layout.csx" codeFormat="commonjs"
|
|
220
|
+
require("./globals.css");
|
|
221
|
+
|
|
222
|
+
const RootLayout = ({ children }) => (
|
|
223
|
+
// Você ainda pode envolver os filhos com outros provedores, como `next-themes`, `react-query`, `framer-motion`, etc.
|
|
224
|
+
<>{children}</>
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
default: RootLayout,
|
|
229
|
+
generateStaticParams,
|
|
230
|
+
};
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> Manter o componente `RootLayout` vazio permite definir os atributos [`lang`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/lang) e [`dir`](https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/dir) na tag `<html>`.
|
|
234
|
+
|
|
235
|
+
Para implementar o roteamento dinâmico, forneça o caminho para o locale adicionando um novo layout no seu diretório `[locale]`:
|
|
236
|
+
|
|
237
|
+
```tsx fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
238
|
+
import type { NextLayoutIntlayer } from "next-intlayer";
|
|
239
|
+
import { Inter } from "next/font/google";
|
|
240
|
+
import { getHTMLTextDir } from "intlayer";
|
|
241
|
+
|
|
242
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
243
|
+
|
|
244
|
+
const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
245
|
+
const { locale } = await params;
|
|
246
|
+
return (
|
|
247
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
248
|
+
<body className={inter.className}>{children}</body>
|
|
249
|
+
</html>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default LocaleLayout;
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```jsx fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
257
|
+
import { getHTMLTextDir } from "intlayer";
|
|
258
|
+
|
|
259
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
260
|
+
|
|
261
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
262
|
+
const { locale } = await params;
|
|
263
|
+
return (
|
|
264
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
265
|
+
<body className={inter.className}>{children}</body>
|
|
266
|
+
</html>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default LocaleLayout;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
```jsx fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
274
|
+
const { Inter } = require("next/font/google");
|
|
275
|
+
const { getHTMLTextDir } = require("intlayer");
|
|
276
|
+
|
|
277
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
278
|
+
|
|
279
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
280
|
+
const { locale } = await params;
|
|
281
|
+
return (
|
|
282
|
+
<html lang={locale} dir={getHTMLTextDir(locale)}>
|
|
283
|
+
<body className={inter.className}>{children}</body>
|
|
284
|
+
</html>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
module.exports = LocaleLayout;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
> O segmento de caminho `[locale]` é usado para definir o locale. Exemplo: `/en-US/about` se referirá a `en-US` e `/fr/about` a `fr`.
|
|
292
|
+
|
|
293
|
+
> Nesta fase, você encontrará o erro: `Error: Missing <html> and <body> tags in the root layout.`. Isso é esperado porque o arquivo `/app/page.tsx` não está mais em uso e pode ser removido. Em vez disso, o segmento de caminho `[locale]` ativará a página `/app/[locale]/page.tsx`. Consequentemente, as páginas estarão acessíveis via caminhos como `/en`, `/fr`, `/es` no seu navegador. Para definir o locale padrão como a página raiz, consulte a configuração do `proxy` no passo 7.
|
|
294
|
+
|
|
295
|
+
Em seguida, implemente a função `generateStaticParams` no Layout da sua aplicação.
|
|
296
|
+
|
|
297
|
+
```tsx {1} fileName="src/app/[locale]/layout.tsx" codeFormat="typescript"
|
|
298
|
+
export { generateStaticParams } from "next-intlayer"; // Linha a inserir
|
|
299
|
+
|
|
300
|
+
const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
|
|
301
|
+
/*... Resto do código*/
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export default LocaleLayout;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```jsx {1} fileName="src/app/[locale]/layout.mjx" codeFormat="esm"
|
|
308
|
+
export { generateStaticParams } from "next-intlayer"; // Linha a inserir
|
|
309
|
+
|
|
310
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
311
|
+
/*... Resto do código*/
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// ... Resto do código
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
```jsx {1,7} fileName="src/app/[locale]/layout.csx" codeFormat="commonjs"
|
|
318
|
+
const { generateStaticParams } = require("next-intlayer"); // Linha a inserir
|
|
319
|
+
|
|
320
|
+
const LocaleLayout = async ({ children, params: { locale } }) => {
|
|
321
|
+
/*... Resto do código*/
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
module.exports = { default: LocaleLayout, generateStaticParams };
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
> `generateStaticParams` garante que sua aplicação pré-construa as páginas necessárias para todos os idiomas, reduzindo o cálculo em tempo de execução e melhorando a experiência do usuário. Para mais detalhes, consulte a [documentação do Next.js sobre generateStaticParams](https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering#generate-static-params).
|
|
328
|
+
|
|
329
|
+
> O Intlayer funciona com `export const dynamic = 'force-static';` para garantir que as páginas sejam pré-construídas para todos os idiomas.
|
|
330
|
+
|
|
331
|
+
### Passo 5: Declare Seu Conteúdo
|
|
332
|
+
|
|
333
|
+
Crie e gerencie suas declarações de conteúdo para armazenar traduções:
|
|
334
|
+
|
|
335
|
+
```tsx fileName="src/app/[locale]/page.content.ts" contentDeclarationFormat="typescript"
|
|
336
|
+
import { t, type Dictionary } from "intlayer";
|
|
337
|
+
|
|
338
|
+
const pageContent = {
|
|
339
|
+
key: "page",
|
|
340
|
+
content: {
|
|
341
|
+
getStarted: {
|
|
342
|
+
main: t({
|
|
343
|
+
en: "Get started by editing",
|
|
344
|
+
fr: "Commencez par éditer",
|
|
345
|
+
es: "Comience por editar",
|
|
346
|
+
}),
|
|
347
|
+
pageLink: "src/app/page.tsx",
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
} satisfies Dictionary;
|
|
351
|
+
|
|
352
|
+
export default pageContent;
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
```javascript fileName="src/app/[locale]/page.content.mjs" contentDeclarationFormat="esm"
|
|
356
|
+
import { t } from "intlayer";
|
|
357
|
+
|
|
358
|
+
/** @type {import('intlayer').Dictionary} */
|
|
359
|
+
// Conteúdo da página com traduções
|
|
360
|
+
const pageContent = {
|
|
361
|
+
key: "page",
|
|
362
|
+
content: {
|
|
363
|
+
getStarted: {
|
|
364
|
+
main: t({
|
|
365
|
+
en: "Get started by editing",
|
|
366
|
+
fr: "Commencez par éditer",
|
|
367
|
+
es: "Comience por editar",
|
|
368
|
+
}),
|
|
369
|
+
pageLink: "src/app/page.tsx",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export default pageContent;
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```javascript fileName="src/app/[locale]/page.content.cjs" contentDeclarationFormat="commonjs"
|
|
378
|
+
const { t } = require("intlayer");
|
|
379
|
+
|
|
380
|
+
/** @type {import('intlayer').Dictionary} */
|
|
381
|
+
const pageContent = {
|
|
382
|
+
key: "page",
|
|
383
|
+
content: {
|
|
384
|
+
getStarted: {
|
|
385
|
+
main: t({
|
|
386
|
+
en: "Get started by editing",
|
|
387
|
+
fr: "Commencez par éditer",
|
|
388
|
+
es: "Comience por editar",
|
|
389
|
+
pt: "Comece editando",
|
|
390
|
+
}),
|
|
391
|
+
pageLink: "src/app/page.tsx",
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
module.exports = pageContent;
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```json fileName="src/app/[locale]/page.content.json" contentDeclarationFormat="json"
|
|
400
|
+
{
|
|
401
|
+
"$schema": "https://intlayer.org/schema.json",
|
|
402
|
+
"key": "page",
|
|
403
|
+
"content": {
|
|
404
|
+
"getStarted": {
|
|
405
|
+
"nodeType": "translation",
|
|
406
|
+
"translation": {
|
|
407
|
+
"en": "Get started by editing",
|
|
408
|
+
"fr": "Commencez par éditer",
|
|
409
|
+
"es": "Comience por editar",
|
|
410
|
+
"pt": "Comece editando"
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
"pageLink": "src/app/page.tsx"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
> Suas declarações de conteúdo podem ser definidas em qualquer lugar da sua aplicação, desde que estejam incluídas no diretório `contentDir` (por padrão, `./src`). E correspondam à extensão do arquivo de declaração de conteúdo (por padrão, `.content.{json,ts,tsx,js,jsx,mjs,mjx,cjs,cjx}`).
|
|
419
|
+
|
|
420
|
+
> Para mais detalhes, consulte a [documentação de declaração de conteúdo](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/dictionary/content_file.md).
|
|
421
|
+
|
|
422
|
+
### Passo 6: Utilize o Conteúdo no Seu Código
|
|
423
|
+
|
|
424
|
+
Acesse seus dicionários de conteúdo em toda a sua aplicação:
|
|
425
|
+
|
|
426
|
+
```tsx fileName="src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
427
|
+
import type { FC } from "react";
|
|
428
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
429
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
430
|
+
import { type NextPageIntlayer, IntlayerClientProvider } from "next-intlayer";
|
|
431
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
432
|
+
|
|
433
|
+
const PageContent: FC = () => {
|
|
434
|
+
const content = useIntlayer("page");
|
|
435
|
+
|
|
436
|
+
return (
|
|
437
|
+
<>
|
|
438
|
+
<p>{content.getStarted.main}</p>
|
|
439
|
+
<code>{content.getStarted.pageLink}</code>
|
|
440
|
+
</>
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const Page: NextPageIntlayer = async ({ params }) => {
|
|
445
|
+
const { locale } = await params;
|
|
446
|
+
|
|
447
|
+
return (
|
|
448
|
+
<IntlayerServerProvider locale={locale}>
|
|
449
|
+
<PageContent />
|
|
450
|
+
<ServerComponentExample />
|
|
451
|
+
|
|
452
|
+
<IntlayerClientProvider locale={locale}>
|
|
453
|
+
<ClientComponentExample />
|
|
454
|
+
</IntlayerClientProvider>
|
|
455
|
+
</IntlayerServerProvider>
|
|
456
|
+
);
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
export default Page;
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
```jsx fileName="src/app/[locale]/page.mjx" codeFormat="esm"
|
|
463
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
464
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
465
|
+
import { IntlayerClientProvider } from "next-intlayer";
|
|
466
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
467
|
+
|
|
468
|
+
const PageContent = () => {
|
|
469
|
+
const content = useIntlayer("page");
|
|
470
|
+
|
|
471
|
+
return (
|
|
472
|
+
<>
|
|
473
|
+
<p>{content.getStarted.main}</p>
|
|
474
|
+
<code>{content.getStarted.pageLink}</code>
|
|
475
|
+
</>
|
|
476
|
+
);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const Page = async ({ params }) => {
|
|
480
|
+
const { locale } = await params;
|
|
481
|
+
|
|
482
|
+
return (
|
|
483
|
+
<IntlayerServerProvider locale={locale}>
|
|
484
|
+
<PageContent />
|
|
485
|
+
<ServerComponentExample />
|
|
486
|
+
|
|
487
|
+
<IntlayerClientProvider locale={locale}>
|
|
488
|
+
<ClientComponentExample />
|
|
489
|
+
</IntlayerClientProvider>
|
|
490
|
+
</IntlayerServerProvider>
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
export default Page;
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```jsx fileName="src/app/[locale]/page.csx" codeFormat="commonjs"
|
|
498
|
+
import { ClientComponentExample } from "@components/ClientComponentExample";
|
|
499
|
+
import { ServerComponentExample } from "@components/ServerComponentExample";
|
|
500
|
+
import { IntlayerClientProvider } from "next-intlayer";
|
|
501
|
+
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
|
|
502
|
+
|
|
503
|
+
const PageContent = () => {
|
|
504
|
+
const content = useIntlayer("page");
|
|
505
|
+
|
|
506
|
+
return (
|
|
507
|
+
<>
|
|
508
|
+
<p>{content.getStarted.main}</p>
|
|
509
|
+
<code>{content.getStarted.pageLink}</code>
|
|
510
|
+
</>
|
|
511
|
+
);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const Page = async ({ params }) => {
|
|
515
|
+
const { locale } = await params;
|
|
516
|
+
|
|
517
|
+
return (
|
|
518
|
+
<IntlayerServerProvider locale={locale}>
|
|
519
|
+
<PageContent />
|
|
520
|
+
<ServerComponentExample />
|
|
521
|
+
|
|
522
|
+
<IntlayerClientProvider locale={locale}>
|
|
523
|
+
<ClientComponentExample />
|
|
524
|
+
</IntlayerClientProvider>
|
|
525
|
+
</IntlayerServerProvider>
|
|
526
|
+
);
|
|
527
|
+
};
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
- **`IntlayerClientProvider`** é usado para fornecer a localidade para componentes do lado do cliente. Pode ser colocado em qualquer componente pai, incluindo o layout. No entanto, recomenda-se colocá-lo em um layout porque o Next.js compartilha o código do layout entre as páginas, tornando-o mais eficiente. Ao usar `IntlayerClientProvider` no layout, você evita reinicializá-lo para cada página, melhorando o desempenho e mantendo um contexto de localização consistente em toda a sua aplicação.
|
|
531
|
+
- **`IntlayerServerProvider`** é usado para fornecer a localidade para os filhos do servidor. Ele não pode ser definido no layout.
|
|
532
|
+
|
|
533
|
+
> Layout e página não podem compartilhar um contexto de servidor comum porque o sistema de contexto do servidor é baseado em um armazenamento de dados por requisição (via mecanismo de [cache do React](https://react.dev/reference/react/cache)), fazendo com que cada "contexto" seja recriado para diferentes segmentos da aplicação. Colocar o provider em um layout compartilhado quebraria esse isolamento, impedindo a propagação correta dos valores do contexto do servidor para seus componentes de servidor.
|
|
534
|
+
|
|
535
|
+
```tsx {4,7} fileName="src/components/ClientComponentExample.tsx" codeFormat="typescript"
|
|
536
|
+
"use client";
|
|
537
|
+
|
|
538
|
+
import type { FC } from "react";
|
|
539
|
+
import { useIntlayer } from "next-intlayer";
|
|
540
|
+
|
|
541
|
+
export const ClientComponentExample: FC = () => {
|
|
542
|
+
const content = useIntlayer("client-component-example"); // Criar declaração de conteúdo relacionada
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<div>
|
|
546
|
+
<h2>{content.title}</h2>
|
|
547
|
+
<p>{content.content}</p>
|
|
548
|
+
</div>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
```jsx {3,6} fileName="src/components/ClientComponentExample.mjx" codeFormat="esm"
|
|
554
|
+
"use client";
|
|
555
|
+
|
|
556
|
+
import { useIntlayer } from "next-intlayer";
|
|
557
|
+
|
|
558
|
+
const ClientComponentExample = () => {
|
|
559
|
+
const content = useIntlayer("client-component-example"); // Criar declaração de conteúdo relacionado
|
|
560
|
+
|
|
561
|
+
return (
|
|
562
|
+
<div>
|
|
563
|
+
<h2>{content.title}</h2>
|
|
564
|
+
<p>{content.content}</p>
|
|
565
|
+
</div>
|
|
566
|
+
);
|
|
567
|
+
};
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
```jsx {3,6} fileName="src/components/ClientComponentExample.csx" codeFormat="commonjs"
|
|
571
|
+
"use client";
|
|
572
|
+
|
|
573
|
+
const { useIntlayer } = require("next-intlayer");
|
|
574
|
+
|
|
575
|
+
const ClientComponentExample = () => {
|
|
576
|
+
const content = useIntlayer("client-component-example"); // Criar declaração de conteúdo relacionado
|
|
577
|
+
|
|
578
|
+
return (
|
|
579
|
+
<div>
|
|
580
|
+
<h2>{content.title}</h2>
|
|
581
|
+
<p>{content.content}</p>
|
|
582
|
+
</div>
|
|
583
|
+
);
|
|
584
|
+
};
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
```tsx {2} fileName="src/components/ServerComponentExample.tsx" codeFormat="typescript"
|
|
588
|
+
import type { FC } from "react";
|
|
589
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
590
|
+
|
|
591
|
+
export const ServerComponentExample: FC = () => {
|
|
592
|
+
const content = useIntlayer("server-component-example"); // Criar declaração de conteúdo relacionado
|
|
593
|
+
|
|
594
|
+
return (
|
|
595
|
+
<div>
|
|
596
|
+
<h2>{content.title}</h2>
|
|
597
|
+
<p>{content.content}</p>
|
|
598
|
+
</div>
|
|
599
|
+
);
|
|
600
|
+
};
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
```jsx {1} fileName="src/components/ServerComponentExample.mjx" codeFormat="esm"
|
|
604
|
+
import { useIntlayer } from "next-intlayer/server";
|
|
605
|
+
|
|
606
|
+
const ServerComponentExample = () => {
|
|
607
|
+
const content = useIntlayer("server-component-example"); // Criar declaração de conteúdo relacionado
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<div>
|
|
611
|
+
<h2>{content.title}</h2>
|
|
612
|
+
<p>{content.content}</p>
|
|
613
|
+
</div>
|
|
614
|
+
);
|
|
615
|
+
};
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
```jsx {1} fileName="src/components/ServerComponentExample.csx" codeFormat="commonjs"
|
|
619
|
+
const { useIntlayer } = require("next-intlayer/server");
|
|
620
|
+
|
|
621
|
+
const ServerComponentExample = () => {
|
|
622
|
+
const content = useIntlayer("server-component-example"); // Criar declaração de conteúdo relacionada
|
|
623
|
+
|
|
624
|
+
return (
|
|
625
|
+
<div>
|
|
626
|
+
<h2>{content.title}</h2>
|
|
627
|
+
<p>{content.content}</p>
|
|
628
|
+
</div>
|
|
629
|
+
);
|
|
630
|
+
};
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
> Se quiser usar seu conteúdo em um atributo `string`, como `alt`, `title`, `href`, `aria-label`, etc., você deve chamar o valor da função, assim:
|
|
634
|
+
|
|
635
|
+
> ```jsx
|
|
636
|
+
> <img src={content.image.src.value} alt={content.image.value} />
|
|
637
|
+
> ```
|
|
638
|
+
|
|
639
|
+
> Para saber mais sobre o hook `useIntlayer`, consulte a [documentação](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/next-intlayer/useIntlayer.md).
|
|
640
|
+
|
|
641
|
+
### (Opcional) Passo 7: Configurar Proxy para Detecção de Localidade
|
|
642
|
+
|
|
643
|
+
Configure o proxy para detectar a localidade preferida do usuário:
|
|
644
|
+
|
|
645
|
+
```typescript fileName="src/proxy.ts" codeFormat="typescript"
|
|
646
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
647
|
+
|
|
648
|
+
export const config = {
|
|
649
|
+
matcher:
|
|
650
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
651
|
+
};
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
```javascript fileName="src/proxy.mjs" codeFormat="esm"
|
|
655
|
+
export { intlayerProxy as proxy } from "next-intlayer/proxy";
|
|
656
|
+
|
|
657
|
+
export const config = {
|
|
658
|
+
matcher:
|
|
659
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
660
|
+
};
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
```javascript fileName="src/proxy.cjs" codeFormat="commonjs"
|
|
664
|
+
const { intlayerProxy } = require("next-intlayer/proxy");
|
|
665
|
+
|
|
666
|
+
const config = {
|
|
667
|
+
matcher:
|
|
668
|
+
"/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\\..*|_next).*)",
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
module.exports = { proxy: intlayerProxy, config };
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
> O `intlayerProxy` é usado para detectar a localidade preferida do usuário e redirecioná-lo para a URL apropriada conforme especificado na [configuração](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/configuration.md). Além disso, permite salvar a localidade preferida do usuário em um cookie.
|
|
675
|
+
|
|
676
|
+
> Se você precisar encadear vários proxies juntos (por exemplo, `intlayerProxy` com autenticação ou proxies personalizados), o Intlayer agora fornece um auxiliar chamado `multipleProxies`.
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
import { multipleProxies, intlayerProxy } from "next-intlayer/proxy";
|
|
680
|
+
import { customProxy } from "@utils/customProxy";
|
|
681
|
+
|
|
682
|
+
export const proxy = multipleProxies([intlayerProxy, customProxy]);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### (Opcional) Passo 8: Internacionalização dos seus metadados
|
|
686
|
+
|
|
687
|
+
Caso queira internacionalizar seus metadados, como o título da sua página, você pode usar a função `generateMetadata` fornecida pelo Next.js. Dentro dela, você pode recuperar o conteúdo da função `getIntlayer` para traduzir seus metadados.
|
|
688
|
+
|
|
689
|
+
```typescript fileName="src/app/[locale]/metadata.content.ts" contentDeclarationFormat="typescript"
|
|
690
|
+
import { type Dictionary, t } from "intlayer";
|
|
691
|
+
import { Metadata } from "next";
|
|
692
|
+
|
|
693
|
+
const metadataContent = {
|
|
694
|
+
key: "page-metadata",
|
|
695
|
+
content: {
|
|
696
|
+
title: t({
|
|
697
|
+
en: "Create Next App",
|
|
698
|
+
fr: "Créer une application Next.js",
|
|
699
|
+
es: "Crear una aplicación Next.js",
|
|
700
|
+
}),
|
|
701
|
+
description: t({
|
|
702
|
+
en: "Generated by create next app",
|
|
703
|
+
fr: "Généré par create next app",
|
|
704
|
+
es: "Generado por create next app",
|
|
705
|
+
}),
|
|
706
|
+
},
|
|
707
|
+
} satisfies Dictionary<Metadata>;
|
|
708
|
+
|
|
709
|
+
export default metadataContent;
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
```javascript fileName="src/app/[locale]/metadata.content.mjs" contentDeclarationFormat="esm"
|
|
713
|
+
import { t } from "intlayer";
|
|
714
|
+
|
|
715
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
716
|
+
const metadataContent = {
|
|
717
|
+
key: "page-metadata",
|
|
718
|
+
content: {
|
|
719
|
+
title: t({
|
|
720
|
+
en: "Create Next App",
|
|
721
|
+
fr: "Créer une application Next.js",
|
|
722
|
+
es: "Crear una aplicación Next.js",
|
|
723
|
+
}),
|
|
724
|
+
description: t({
|
|
725
|
+
en: "Generated by create next app",
|
|
726
|
+
fr: "Généré par create next app",
|
|
727
|
+
es: "Generado por create next app",
|
|
728
|
+
}),
|
|
729
|
+
},
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
export default metadataContent;
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
736
|
+
const { t } = require("intlayer");
|
|
737
|
+
|
|
738
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
739
|
+
const metadataContent = {
|
|
740
|
+
key: "page-metadata",
|
|
741
|
+
content: {
|
|
742
|
+
title: t({
|
|
743
|
+
en: "Create Next App",
|
|
744
|
+
fr: "Créer une application Next.js",
|
|
745
|
+
es: "Crear una aplicación Next.js",
|
|
746
|
+
}),
|
|
747
|
+
description: t({
|
|
748
|
+
en: "Generated by create next app",
|
|
749
|
+
fr: "Généré par create next app",
|
|
750
|
+
es: "Generado por create next app",
|
|
751
|
+
}),
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
module.exports = metadataContent;
|
|
756
|
+
fr: "Généré par create next app",
|
|
757
|
+
es: "Generado por create next app",
|
|
758
|
+
pt: "Gerado por create next app",
|
|
759
|
+
}),
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
export default metadataContent;
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
```javascript fileName="src/app/[locale]/metadata.content.cjs" contentDeclarationFormat="commonjs"
|
|
767
|
+
const { t } = require("intlayer");
|
|
768
|
+
|
|
769
|
+
/** @type {import('intlayer').Dictionary<import('next').Metadata>} */
|
|
770
|
+
const metadataContent = {
|
|
771
|
+
key: "page-metadata",
|
|
772
|
+
content: {
|
|
773
|
+
title: t({
|
|
774
|
+
en: "Create Next App",
|
|
775
|
+
fr: "Créer une application Next.js",
|
|
776
|
+
es: "Crear una aplicación Next.js",
|
|
777
|
+
pt: "Criar uma aplicação Next.js",
|
|
778
|
+
}),
|
|
779
|
+
description: t({
|
|
780
|
+
en: "Generated by create next app",
|
|
781
|
+
fr: "Généré par create next app",
|
|
782
|
+
es: "Generado por create next app",
|
|
783
|
+
pt: "Gerado por create next app",
|
|
784
|
+
}),
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
module.exports = metadataContent;
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
```json fileName="src/app/[locale]/metadata.content.json" contentDeclarationFormat="json"
|
|
792
|
+
{
|
|
793
|
+
"key": "page-metadata",
|
|
794
|
+
"content": {
|
|
795
|
+
"title": {
|
|
796
|
+
"nodeType": "translation",
|
|
797
|
+
"translation": {
|
|
798
|
+
"pt": "Logo Preact",
|
|
799
|
+
"en": "Preact logo",
|
|
800
|
+
"fr": "Logo Preact",
|
|
801
|
+
"es": "Logo Preact"
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
"description": {
|
|
805
|
+
"nodeType": "translation",
|
|
806
|
+
"translation": {
|
|
807
|
+
"pt": "Gerado por create next app",
|
|
808
|
+
"en": "Generated by create next app",
|
|
809
|
+
"fr": "Généré par create next app",
|
|
810
|
+
"es": "Generado por create next app"
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
818
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
819
|
+
import type { Metadata } from "next";
|
|
820
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
821
|
+
|
|
822
|
+
export const generateMetadata = async ({
|
|
823
|
+
params,
|
|
824
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
825
|
+
const { locale } = await params;
|
|
826
|
+
|
|
827
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Gera um objeto contendo todas as URLs para cada localidade.
|
|
831
|
+
*
|
|
832
|
+
* Exemplo:
|
|
833
|
+
* ```ts
|
|
834
|
+
* getMultilingualUrls('/about');
|
|
835
|
+
*
|
|
836
|
+
* // Retorna
|
|
837
|
+
* // {
|
|
838
|
+
* // en: '/about',
|
|
839
|
+
* // fr: '/fr/about',
|
|
840
|
+
* // es: '/es/about',
|
|
841
|
+
* // }
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
845
|
+
|
|
846
|
+
return {
|
|
847
|
+
...metadata,
|
|
848
|
+
alternates: {
|
|
849
|
+
canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
|
|
850
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
851
|
+
},
|
|
852
|
+
openGraph: {
|
|
853
|
+
url: multilingualUrls[locale],
|
|
854
|
+
},
|
|
855
|
+
};
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// ... Resto do código
|
|
859
|
+
````
|
|
860
|
+
|
|
861
|
+
````javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
|
|
862
|
+
import { getIntlayer, getMultilingualUrls } from "intlayer";
|
|
863
|
+
|
|
864
|
+
export const generateMetadata = async ({ params }) => {
|
|
865
|
+
const { locale } = await params;
|
|
866
|
+
|
|
867
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Gera um objeto contendo todas as URLs para cada localidade.
|
|
871
|
+
*
|
|
872
|
+
* Exemplo:
|
|
873
|
+
* ```ts
|
|
874
|
+
* getMultilingualUrls('/about');
|
|
875
|
+
*
|
|
876
|
+
* // Retorna
|
|
877
|
+
* // {
|
|
878
|
+
* // en: '/about',
|
|
879
|
+
* // fr: '/fr/about',
|
|
880
|
+
* // es: '/es/about'
|
|
881
|
+
* // }
|
|
882
|
+
* ```
|
|
883
|
+
*/
|
|
884
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
885
|
+
|
|
886
|
+
return {
|
|
887
|
+
...metadata,
|
|
888
|
+
alternates: {
|
|
889
|
+
canonical: multilingualUrls[locale],
|
|
890
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
891
|
+
},
|
|
892
|
+
openGraph: {
|
|
893
|
+
url: multilingualUrls[locale],
|
|
894
|
+
},
|
|
895
|
+
};
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
// ... Resto do código
|
|
899
|
+
````
|
|
900
|
+
|
|
901
|
+
````javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
|
|
902
|
+
const { getIntlayer, getMultilingualUrls } = require("intlayer");
|
|
903
|
+
|
|
904
|
+
const generateMetadata = async ({ params }) => {
|
|
905
|
+
const { locale } = await params;
|
|
906
|
+
|
|
907
|
+
const metadata = getIntlayer("page-metadata", locale);
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Gera um objeto contendo todas as URLs para cada localidade.
|
|
911
|
+
*
|
|
912
|
+
* Exemplo:
|
|
913
|
+
* ```ts
|
|
914
|
+
* getMultilingualUrls('/about');
|
|
915
|
+
*
|
|
916
|
+
* // Retorna
|
|
917
|
+
* // {
|
|
918
|
+
* // en: '/about',
|
|
919
|
+
* // fr: '/fr/about',
|
|
920
|
+
* // es: '/es/about'
|
|
921
|
+
* // }
|
|
922
|
+
* ```
|
|
923
|
+
*/
|
|
924
|
+
const multilingualUrls = getMultilingualUrls("/");
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
...metadata,
|
|
928
|
+
alternates: {
|
|
929
|
+
canonical: multilingualUrls[locale],
|
|
930
|
+
languages: { ...multilingualUrls, "x-default": "/" },
|
|
931
|
+
},
|
|
932
|
+
openGraph: {
|
|
933
|
+
url: multilingualUrls[locale],
|
|
934
|
+
},
|
|
935
|
+
};
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
module.exports = { generateMetadata };
|
|
939
|
+
|
|
940
|
+
// ... Resto do código
|
|
941
|
+
````
|
|
942
|
+
|
|
943
|
+
> Note que a função `getIntlayer` importada de `next-intlayer` retorna seu conteúdo encapsulado em um `IntlayerNode`, permitindo a integração com o editor visual. Em contraste, a função `getIntlayer` importada de `intlayer` retorna seu conteúdo diretamente, sem propriedades adicionais.
|
|
944
|
+
|
|
945
|
+
Alternativamente, você pode usar a função `getTranslation` para declarar seus metadados. No entanto, é recomendável usar arquivos de declaração de conteúdo para automatizar a tradução dos seus metadados e externalizar o conteúdo em algum momento.
|
|
946
|
+
|
|
947
|
+
````typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
948
|
+
import {
|
|
949
|
+
type IConfigLocales,
|
|
950
|
+
getTranslation,
|
|
951
|
+
getMultilingualUrls,
|
|
952
|
+
} from "intlayer";
|
|
953
|
+
import type { Metadata } from "next";
|
|
954
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
955
|
+
|
|
956
|
+
export const generateMetadata = async ({
|
|
957
|
+
params,
|
|
958
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
959
|
+
const { locale } = await params;
|
|
960
|
+
const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
|
|
961
|
+
|
|
962
|
+
return {
|
|
963
|
+
title: t<string>({
|
|
964
|
+
en: "My title",
|
|
965
|
+
Alternativamente, você pode usar a função `getTranslation` para declarar seus metadados. No entanto, é recomendado usar arquivos de declaração de conteúdo para automatizar a tradução dos seus metadados e externalizar o conteúdo em algum momento.
|
|
966
|
+
|
|
967
|
+
```typescript fileName="src/app/[locale]/layout.tsx or src/app/[locale]/page.tsx" codeFormat="typescript"
|
|
968
|
+
import {
|
|
969
|
+
type IConfigLocales,
|
|
970
|
+
getTranslation,
|
|
971
|
+
getMultilingualUrls,
|
|
972
|
+
} from "intlayer";
|
|
973
|
+
import type { Metadata } from "next";
|
|
974
|
+
import type { LocalPromiseParams } from "next-intlayer";
|
|
975
|
+
|
|
976
|
+
export const generateMetadata = async ({
|
|
977
|
+
params,
|
|
978
|
+
}: LocalPromiseParams): Promise<Metadata> => {
|
|
979
|
+
const { locale } = await params;
|
|
980
|
+
const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
title: t<string>({
|
|
984
|
+
en: "My title",
|
|
985
|
+
fr: "Mon titre",
|
|
986
|
+
es: "Mi título",
|
|
987
|
+
}),
|
|
988
|
+
description: t({
|
|
989
|
+
en: "My description",
|
|
990
|
+
fr: "Ma description",
|
|
991
|
+
es: "Mi descripción",
|
|
992
|
+
}),
|
|
993
|
+
};
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// ... Resto do código
|
|
997
|
+
````
|
|
998
|
+
|
|
999
|
+
```javascript fileName="src/app/[locale]/layout.mjs or src/app/[locale]/page.mjs" codeFormat="esm"
|
|
1000
|
+
import { getTranslation, getMultilingualUrls } from "intlayer";
|
|
1001
|
+
|
|
1002
|
+
export const generateMetadata = async ({ params }) => {
|
|
1003
|
+
const { locale } = await params;
|
|
1004
|
+
const t = (content) => getTranslation(content, locale);
|
|
1005
|
+
|
|
1006
|
+
return {
|
|
1007
|
+
title: t({
|
|
1008
|
+
en: "My title",
|
|
1009
|
+
fr: "Mon titre",
|
|
1010
|
+
es: "Mi título",
|
|
1011
|
+
}),
|
|
1012
|
+
description: t({
|
|
1013
|
+
en: "My description",
|
|
1014
|
+
fr: "Ma description",
|
|
1015
|
+
es: "Mi descripción",
|
|
1016
|
+
}),
|
|
1017
|
+
};
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
// ... Resto do código
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
```javascript fileName="src/app/[locale]/layout.cjs or src/app/[locale]/page.cjs" codeFormat="commonjs"
|
|
1024
|
+
const { getTranslation, getMultilingualUrls } = require("intlayer");
|
|
1025
|
+
|
|
1026
|
+
const generateMetadata = async ({ params }) => {
|
|
1027
|
+
const { locale } = await params;
|
|
1028
|
+
|
|
1029
|
+
const t = (content) => getTranslation(content, locale);
|
|
1030
|
+
|
|
1031
|
+
return {
|
|
1032
|
+
title: t({
|
|
1033
|
+
en: "My title",
|
|
1034
|
+
fr: "Mon titre",
|
|
1035
|
+
es: "Mi título",
|
|
1036
|
+
}),
|
|
1037
|
+
description: t({
|
|
1038
|
+
en: "My description",
|
|
1039
|
+
fr: "Ma description",
|
|
1040
|
+
es: "Mi descripción",
|
|
1041
|
+
}),
|
|
1042
|
+
};
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1045
|
+
module.exports = { generateMetadata };
|
|
1046
|
+
|
|
1047
|
+
// ... Resto do código
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
> Saiba mais sobre a otimização de metadados [na documentação oficial do Next.js](https://nextjs.org/docs/app/building-your-application/optimizing/metadata).
|
|
1051
|
+
|
|
1052
|
+
### (Opcional) Passo 9: Internacionalização do seu sitemap.xml e robots.txt
|
|
1053
|
+
|
|
1054
|
+
Para internacionalizar o seu `sitemap.xml` e `robots.txt`, você pode usar a função `getMultilingualUrls` fornecida pelo Intlayer. Esta função permite gerar URLs multilíngues para o seu sitemap.
|
|
1055
|
+
|
|
1056
|
+
```tsx fileName="src/app/sitemap.ts" codeFormat="typescript"
|
|
1057
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1058
|
+
import type { MetadataRoute } from "next";
|
|
1059
|
+
|
|
1060
|
+
const sitemap = (): MetadataRoute.Sitemap => [
|
|
1061
|
+
{
|
|
1062
|
+
url: "https://example.com",
|
|
1063
|
+
alternates: {
|
|
1064
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
url: "https://example.com/login",
|
|
1069
|
+
alternates: {
|
|
1070
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1071
|
+
},
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
url: "https://example.com/register",
|
|
1075
|
+
alternates: {
|
|
1076
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
];
|
|
1080
|
+
|
|
1081
|
+
export default sitemap;
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
```jsx fileName="src/app/sitemap.mjx" codeFormat="esm"
|
|
1085
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1086
|
+
|
|
1087
|
+
const sitemap = () => [
|
|
1088
|
+
{
|
|
1089
|
+
url: "https://example.com",
|
|
1090
|
+
alternates: {
|
|
1091
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
{
|
|
1095
|
+
url: "https://example.com/login",
|
|
1096
|
+
alternates: {
|
|
1097
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
url: "https://example.com/register",
|
|
1102
|
+
alternates: {
|
|
1103
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
];
|
|
1107
|
+
|
|
1108
|
+
export default sitemap;
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
```jsx fileName="src/app/sitemap.csx" codeFormat="commonjs"
|
|
1112
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1113
|
+
|
|
1114
|
+
const sitemap = () => [
|
|
1115
|
+
{
|
|
1116
|
+
url: "https://example.com",
|
|
1117
|
+
alternates: {
|
|
1118
|
+
languages: { ...getMultilingualUrls("https://example.com") },
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
url: "https://example.com/login",
|
|
1123
|
+
alternates: {
|
|
1124
|
+
languages: { ...getMultilingualUrls("https://example.com/login") },
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
url: "https://example.com/register",
|
|
1129
|
+
alternates: {
|
|
1130
|
+
languages: { ...getMultilingualUrls("https://example.com/register") },
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
];
|
|
1134
|
+
|
|
1135
|
+
module.exports = sitemap;
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
```tsx fileName="src/app/robots.ts" codeFormat="typescript"
|
|
1139
|
+
import type { MetadataRoute } from "next";
|
|
1140
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1141
|
+
|
|
1142
|
+
const getAllMultilingualUrls = (urls: string[]) =>
|
|
1143
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
|
|
1144
|
+
|
|
1145
|
+
// Obtém todas as URLs multilíngues para as regras do robots.txt
|
|
1146
|
+
const robots = (): MetadataRoute.Robots => ({
|
|
1147
|
+
rules: {
|
|
1148
|
+
userAgent: "*", // Aplica a todos os agentes de usuário
|
|
1149
|
+
allow: ["/"], // Permite acesso à raiz
|
|
1150
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]), // Bloqueia URLs multilíngues de login e registro
|
|
1151
|
+
},
|
|
1152
|
+
host: "https://example.com",
|
|
1153
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
export default robots;
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
```jsx fileName="src/app/robots.mjx" codeFormat="esm"
|
|
1160
|
+
import { getMultilingualUrls } from "intlayer";
|
|
1161
|
+
|
|
1162
|
+
// Função para obter todas as URLs multilíngues de uma lista de URLs
|
|
1163
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1164
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1165
|
+
|
|
1166
|
+
const robots = () => ({
|
|
1167
|
+
rules: {
|
|
1168
|
+
userAgent: "*", // Aplica a todos os agentes de usuário
|
|
1169
|
+
allow: ["/"], // Permite acesso à raiz
|
|
1170
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]), // Bloqueia URLs multilíngues de login e registro
|
|
1171
|
+
},
|
|
1172
|
+
host: "https://example.com",
|
|
1173
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
export default robots;
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
```jsx fileName="src/app/robots.csx" codeFormat="commonjs"
|
|
1180
|
+
const { getMultilingualUrls } = require("intlayer");
|
|
1181
|
+
|
|
1182
|
+
const getAllMultilingualUrls = (urls) =>
|
|
1183
|
+
urls.flatMap((url) => Object.values(getMultilingualUrls(url)));
|
|
1184
|
+
|
|
1185
|
+
const robots = () => ({
|
|
1186
|
+
rules: {
|
|
1187
|
+
userAgent: "*",
|
|
1188
|
+
allow: ["/"],
|
|
1189
|
+
disallow: getAllMultilingualUrls(["/login", "/register"]),
|
|
1190
|
+
},
|
|
1191
|
+
host: "https://example.com",
|
|
1192
|
+
sitemap: `https://example.com/sitemap.xml`,
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
module.exports = robots;
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
> Saiba mais sobre a otimização do sitemap [na documentação oficial do Next.js](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap). Saiba mais sobre a otimização do robots.txt [na documentação oficial do Next.js](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots).
|
|
1199
|
+
|
|
1200
|
+
### (Opcional) Passo 10: Alterar o idioma do seu conteúdo
|
|
1201
|
+
|
|
1202
|
+
Para alterar o idioma do seu conteúdo no Next.js, a forma recomendada é usar o componente `Link` para redirecionar os usuários para a página localizada apropriada. O componente `Link` permite o pré-carregamento da página, o que ajuda a evitar um recarregamento completo da página.
|
|
1203
|
+
|
|
1204
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx" codeFormat="typescript"
|
|
1205
|
+
"use client";
|
|
1206
|
+
|
|
1207
|
+
import type { FC } from "react";
|
|
1208
|
+
import {
|
|
1209
|
+
Locales,
|
|
1210
|
+
getHTMLTextDir,
|
|
1211
|
+
getLocaleName,
|
|
1212
|
+
getLocalizedUrl,
|
|
1213
|
+
} from "intlayer";
|
|
1214
|
+
import { useLocale } from "next-intlayer";
|
|
1215
|
+
import Link from "next/link";
|
|
1216
|
+
|
|
1217
|
+
export const LocaleSwitcher: FC = () => {
|
|
1218
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1219
|
+
useLocale();
|
|
1220
|
+
|
|
1221
|
+
return (
|
|
1222
|
+
<div>
|
|
1223
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1224
|
+
<div id="localePopover" popover="auto">
|
|
1225
|
+
{availableLocales.map((localeItem) => (
|
|
1226
|
+
<Link
|
|
1227
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1228
|
+
key={localeItem}
|
|
1229
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1230
|
+
onClick={() => setLocale(localeItem)}
|
|
1231
|
+
replace // Garantirá que o botão "voltar" do navegador redirecione para a página anterior
|
|
1232
|
+
>
|
|
1233
|
+
<span>
|
|
1234
|
+
{/* Local - ex. FR */}
|
|
1235
|
+
{localeItem}
|
|
1236
|
+
</span>
|
|
1237
|
+
<span>
|
|
1238
|
+
{/* Idioma no seu próprio Local - ex. Français */}
|
|
1239
|
+
{getLocaleName(localeItem, locale)}
|
|
1240
|
+
</span>
|
|
1241
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1242
|
+
{/* Idioma no Local atual - ex. Francés com o local atual definido para Locales.SPANISH */}
|
|
1243
|
+
{getLocaleName(localeItem)}
|
|
1244
|
+
</span>
|
|
1245
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1246
|
+
{/* Idioma em Inglês - ex. French */}
|
|
1247
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1248
|
+
</span>
|
|
1249
|
+
</Link>
|
|
1250
|
+
))}
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
);
|
|
1254
|
+
};
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
```jsx fileName="src/components/LocaleSwitcher.msx" codeFormat="esm"
|
|
1258
|
+
"use client";
|
|
1259
|
+
|
|
1260
|
+
import {
|
|
1261
|
+
Locales,
|
|
1262
|
+
getHTMLTextDir,
|
|
1263
|
+
getLocaleName,
|
|
1264
|
+
getLocalizedUrl,
|
|
1265
|
+
} from "intlayer";
|
|
1266
|
+
import { useLocale } from "next-intlayer";
|
|
1267
|
+
import Link from "next/link";
|
|
1268
|
+
|
|
1269
|
+
export const LocaleSwitcher = () => {
|
|
1270
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1271
|
+
useLocale();
|
|
1272
|
+
|
|
1273
|
+
return (
|
|
1274
|
+
<div>
|
|
1275
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1276
|
+
<div id="localePopover" popover="auto">
|
|
1277
|
+
{availableLocales.map((localeItem) => (
|
|
1278
|
+
<Link
|
|
1279
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1280
|
+
key={localeItem}
|
|
1281
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1282
|
+
onClick={() => setLocale(localeItem)}
|
|
1283
|
+
replace // Garantirá que o botão "voltar" do navegador redirecione para a página anterior
|
|
1284
|
+
>
|
|
1285
|
+
<span>
|
|
1286
|
+
{/* Localidade - ex. FR */}
|
|
1287
|
+
{localeItem}
|
|
1288
|
+
</span>
|
|
1289
|
+
<span>
|
|
1290
|
+
{/* Idioma na sua própria Localidade - ex. Français */}
|
|
1291
|
+
{getLocaleName(localeItem, locale)}
|
|
1292
|
+
</span>
|
|
1293
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1294
|
+
{/* Idioma na Localidade atual - ex. Francés com a localidade atual definida para Locales.SPANISH */}
|
|
1295
|
+
{getLocaleName(localeItem)}
|
|
1296
|
+
</span>
|
|
1297
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1298
|
+
{/* Idioma em Inglês - ex. French */}
|
|
1299
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1300
|
+
</span>
|
|
1301
|
+
</Link>
|
|
1302
|
+
))}
|
|
1303
|
+
</div>
|
|
1304
|
+
</div>
|
|
1305
|
+
);
|
|
1306
|
+
};
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
```jsx fileName="src/components/LocaleSwitcher.csx" codeFormat="commonjs"
|
|
1310
|
+
"use client";
|
|
1311
|
+
|
|
1312
|
+
const {
|
|
1313
|
+
Locales,
|
|
1314
|
+
getHTMLTextDir,
|
|
1315
|
+
getLocaleName,
|
|
1316
|
+
getLocalizedUrl,
|
|
1317
|
+
} = require("intlayer");
|
|
1318
|
+
const { useLocale } = require("next-intlayer");
|
|
1319
|
+
const Link = require("next/link");
|
|
1320
|
+
|
|
1321
|
+
export const LocaleSwitcher = () => {
|
|
1322
|
+
const { locale, pathWithoutLocale, availableLocales, setLocale } =
|
|
1323
|
+
useLocale();
|
|
1324
|
+
|
|
1325
|
+
return (
|
|
1326
|
+
<div>
|
|
1327
|
+
<button popoverTarget="localePopover">{getLocaleName(locale)}</button>
|
|
1328
|
+
<div id="localePopover" popover="auto">
|
|
1329
|
+
{availableLocales.map((localeItem) => (
|
|
1330
|
+
<Link
|
|
1331
|
+
href={getLocalizedUrl(pathWithoutLocale, localeItem)}
|
|
1332
|
+
key={localeItem}
|
|
1333
|
+
aria-current={locale === localeItem ? "page" : undefined}
|
|
1334
|
+
onClick={() => setLocale(localeItem)}
|
|
1335
|
+
replace // Garantirá que o botão "voltar" do navegador redirecione para a página anterior
|
|
1336
|
+
>
|
|
1337
|
+
<span>
|
|
1338
|
+
{/* Local - ex. FR */}
|
|
1339
|
+
{localeItem}
|
|
1340
|
+
</span>
|
|
1341
|
+
<span>
|
|
1342
|
+
{/* Idioma no seu próprio Local - ex. Français */}
|
|
1343
|
+
{getLocaleName(localeItem, locale)}
|
|
1344
|
+
</span>
|
|
1345
|
+
<span dir={getHTMLTextDir(localeItem)} lang={localeItem}>
|
|
1346
|
+
{/* Idioma no Local atual - ex. Francés com o local atual definido para Locales.SPANISH */}
|
|
1347
|
+
{getLocaleName(localeItem)}
|
|
1348
|
+
</span>
|
|
1349
|
+
<span dir="ltr" lang={Locales.ENGLISH}>
|
|
1350
|
+
{/* Idioma em inglês - ex: Francês */}
|
|
1351
|
+
{getLocaleName(localeItem, Locales.ENGLISH)}
|
|
1352
|
+
</span>
|
|
1353
|
+
</Link>
|
|
1354
|
+
))}
|
|
1355
|
+
</div>
|
|
1356
|
+
</div>
|
|
1357
|
+
);
|
|
1358
|
+
};
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
> Uma forma alternativa é usar a função `setLocale` fornecida pelo hook `useLocale`. Essa função não permitirá o pré-carregamento da página. Veja a [documentação do hook `useLocale`](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/next-intlayer/useLocale.md) para mais detalhes.
|
|
1362
|
+
|
|
1363
|
+
> Você também pode definir uma função na opção `onLocaleChange` para disparar uma função personalizada quando o idioma for alterado.
|
|
1364
|
+
|
|
1365
|
+
```tsx fileName="src/components/LocaleSwitcher.tsx"
|
|
1366
|
+
"use client";
|
|
1367
|
+
|
|
1368
|
+
import { useRouter } from "next/navigation";
|
|
1369
|
+
import { useLocale } from "next-intlayer";
|
|
1370
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1371
|
+
|
|
1372
|
+
// ... Resto do código
|
|
1373
|
+
|
|
1374
|
+
const router = useRouter();
|
|
1375
|
+
const { setLocale } = useLocale({
|
|
1376
|
+
onLocaleChange: (locale) => {
|
|
1377
|
+
router.push(getLocalizedUrl(pathWithoutLocale, locale));
|
|
1378
|
+
},
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
return (
|
|
1382
|
+
<button onClick={() => setLocale(Locales.FRENCH)}>Mudar para Francês</button>
|
|
1383
|
+
);
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
> Referências da documentação:
|
|
1387
|
+
>
|
|
1388
|
+
> - [`useLocale` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/next-intlayer/useLocale.md)
|
|
1389
|
+
> - [`getLocaleName` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/intlayer/getLocaleName.md)
|
|
1390
|
+
> - [`getLocalizedUrl` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/intlayer/getLocalizedUrl.md)
|
|
1391
|
+
> - [`getHTMLTextDir` hook](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/packages/intlayer/getHTMLTextDir.md)
|
|
1392
|
+
> - [`hrefLang` attribute](https://developers.google.com/search/docs/specialty/international/localized-versions?hl=fr)
|
|
1393
|
+
> - [`lang` attribute](https://developer.mozilla.org/pt-BR/docs/Web/HTML/Global_attributes/lang)
|
|
1394
|
+
> - [`dir` attribute`](https://developer.mozilla.org/pt-BR/docs/Web/HTML/Global_attributes/dir)
|
|
1395
|
+
> - [`aria-current` attribute`](https://developer.mozilla.org/pt-BR/docs/Web/Accessibility/ARIA/Attributes/aria-current)
|
|
1396
|
+
|
|
1397
|
+
### (Opcional) Passo 11: Criando um Componente de Link Localizado
|
|
1398
|
+
|
|
1399
|
+
Para garantir que a navegação da sua aplicação respeite o idioma atual, você pode criar um componente `Link` personalizado. Esse componente adiciona automaticamente o prefixo do idioma atual às URLs internas. Por exemplo, quando um usuário que fala francês clica em um link para a página "Sobre", ele é redirecionado para `/fr/about` em vez de `/about`.
|
|
1400
|
+
|
|
1401
|
+
Esse comportamento é útil por várias razões:
|
|
1402
|
+
|
|
1403
|
+
- **SEO e Experiência do Usuário**: URLs localizadas ajudam os motores de busca a indexar corretamente páginas específicas por idioma e fornecem aos usuários conteúdo no idioma de sua preferência.
|
|
1404
|
+
- **Consistência**: Ao usar um link localizado em toda a sua aplicação, você garante que a navegação permaneça dentro do idioma atual, evitando mudanças inesperadas de idioma.
|
|
1405
|
+
- **Manutenibilidade**: Centralizar a lógica de localização em um único componente simplifica o gerenciamento das URLs, tornando sua base de código mais fácil de manter e expandir conforme sua aplicação cresce.
|
|
1406
|
+
|
|
1407
|
+
Abaixo está a implementação de um componente `Link` localizado em TypeScript:
|
|
1408
|
+
|
|
1409
|
+
```tsx fileName="src/components/Link.tsx" codeFormat="typescript"
|
|
1410
|
+
"use client";
|
|
1411
|
+
|
|
1412
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1413
|
+
import NextLink, { type LinkProps as NextLinkProps } from "next/link";
|
|
1414
|
+
import { useLocale } from "next-intlayer";
|
|
1415
|
+
import type { PropsWithChildren, FC } from "react";
|
|
1416
|
+
|
|
1417
|
+
/**
|
|
1418
|
+
* Função utilitária para verificar se uma URL fornecida é externa.
|
|
1419
|
+
* Se a URL começar com http:// ou https://, é considerada externa.
|
|
1420
|
+
*/
|
|
1421
|
+
export const checkIsExternalLink = (href?: string): boolean =>
|
|
1422
|
+
/^https?:\/\//.test(href ?? "");
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Um componente Link personalizado que adapta o atributo href com base na localidade atual.
|
|
1426
|
+
* Para links internos, ele usa `getLocalizedUrl` para prefixar a URL com a localidade (ex: /fr/about).
|
|
1427
|
+
* Isso garante que a navegação permaneça dentro do mesmo contexto de localidade.
|
|
1428
|
+
*/
|
|
1429
|
+
export const Link: FC<PropsWithChildren<NextLinkProps>> = ({
|
|
1430
|
+
href,
|
|
1431
|
+
children,
|
|
1432
|
+
...props
|
|
1433
|
+
}) => {
|
|
1434
|
+
const { locale } = useLocale();
|
|
1435
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1436
|
+
|
|
1437
|
+
// Se o link for interno e um href válido for fornecido, obtém a URL localizada.
|
|
1438
|
+
const hrefI18n: NextLinkProps["href"] =
|
|
1439
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1440
|
+
|
|
1441
|
+
return (
|
|
1442
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1443
|
+
{children}
|
|
1444
|
+
</NextLink>
|
|
1445
|
+
);
|
|
1446
|
+
};
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
```jsx fileName="src/components/Link.mjx" codeFormat="esm"
|
|
1450
|
+
"use client";
|
|
1451
|
+
|
|
1452
|
+
import { getLocalizedUrl } from "intlayer";
|
|
1453
|
+
import NextLink from "next/link";
|
|
1454
|
+
import { useLocale } from "next-intlayer";
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Função utilitária para verificar se uma URL é externa.
|
|
1458
|
+
* Se a URL começar com http:// ou https://, é considerada externa.
|
|
1459
|
+
*/
|
|
1460
|
+
export const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Um componente Link personalizado que adapta o atributo href com base na localidade atual.
|
|
1464
|
+
* Para links internos, usa `getLocalizedUrl` para prefixar a URL com a localidade (ex: /fr/about).
|
|
1465
|
+
* Isso garante que a navegação permaneça dentro do mesmo contexto de localidade.
|
|
1466
|
+
*/
|
|
1467
|
+
export const Link = ({ href, children, ...props }) => {
|
|
1468
|
+
const { locale } = useLocale();
|
|
1469
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1470
|
+
|
|
1471
|
+
// Se o link for interno e um href válido for fornecido, obtenha a URL localizada.
|
|
1472
|
+
const hrefI18n =
|
|
1473
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1474
|
+
|
|
1475
|
+
return (
|
|
1476
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1477
|
+
{children}
|
|
1478
|
+
</NextLink>
|
|
1479
|
+
);
|
|
1480
|
+
};
|
|
1481
|
+
```
|
|
1482
|
+
|
|
1483
|
+
```jsx fileName="src/components/Link.csx" codeFormat="commonjs"
|
|
1484
|
+
"use client";
|
|
1485
|
+
|
|
1486
|
+
const { getLocalizedUrl } = require("intlayer");
|
|
1487
|
+
const NextLink = require("next/link");
|
|
1488
|
+
const { useLocale } = require("next-intlayer");
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Função utilitária para verificar se uma URL é externa.
|
|
1492
|
+
* Se a URL começar com http:// ou https://, é considerada externa.
|
|
1493
|
+
*/
|
|
1494
|
+
const checkIsExternalLink = (href) => /^https?:\/\//.test(href ?? "");
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Um componente Link personalizado que adapta o atributo href com base na localidade atual.
|
|
1498
|
+
* Para links internos, ele usa `getLocalizedUrl` para prefixar a URL com a localidade (por exemplo, /fr/about).
|
|
1499
|
+
* Isso garante que a navegação permaneça dentro do mesmo contexto de localidade.
|
|
1500
|
+
*/
|
|
1501
|
+
const Link = ({ href, children, ...props }) => {
|
|
1502
|
+
const { locale } = useLocale();
|
|
1503
|
+
const isExternalLink = checkIsExternalLink(href.toString());
|
|
1504
|
+
|
|
1505
|
+
// Se o link for interno e um href válido for fornecido, obtenha a URL localizada.
|
|
1506
|
+
const hrefI18n =
|
|
1507
|
+
href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;
|
|
1508
|
+
|
|
1509
|
+
return (
|
|
1510
|
+
<NextLink href={hrefI18n} {...props}>
|
|
1511
|
+
{children}
|
|
1512
|
+
</NextLink>
|
|
1513
|
+
);
|
|
1514
|
+
};
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
#### Como Funciona
|
|
1518
|
+
|
|
1519
|
+
- **Detectando Links Externos**:
|
|
1520
|
+
A função auxiliar `checkIsExternalLink` determina se uma URL é externa. Links externos são mantidos inalterados porque não precisam de localização.
|
|
1521
|
+
|
|
1522
|
+
- **Recuperando a Localização Atual**:
|
|
1523
|
+
O hook `useLocale` fornece a localidade atual (por exemplo, `fr` para francês).
|
|
1524
|
+
|
|
1525
|
+
- **Localizando a URL**:
|
|
1526
|
+
Para links internos (ou seja, não externos), `getLocalizedUrl` é usado para prefixar automaticamente a URL com a localidade atual. Isso significa que, se seu usuário estiver em francês, passar `/about` como `href` será transformado em `/fr/about`.
|
|
1527
|
+
|
|
1528
|
+
- **Retornando o Link**:
|
|
1529
|
+
O componente retorna um elemento `<a>` com a URL localizada, garantindo que a navegação seja consistente com a localidade.
|
|
1530
|
+
|
|
1531
|
+
Ao integrar este componente `Link` em toda a sua aplicação, você mantém uma experiência de usuário coerente e consciente do idioma, além de beneficiar-se de uma melhor SEO e usabilidade.
|
|
1532
|
+
|
|
1533
|
+
### (Opcional) Passo 12: Obter o locale atual em Server Actions
|
|
1534
|
+
|
|
1535
|
+
Se você precisar do locale ativo dentro de uma Server Action (por exemplo, para localizar e-mails ou executar lógica sensível ao locale), chame `getLocale` de `next-intlayer/server`:
|
|
1536
|
+
|
|
1537
|
+
```tsx fileName="src/app/actions/getLocale.ts" codeFormat="typescript"
|
|
1538
|
+
"use server";
|
|
1539
|
+
|
|
1540
|
+
import { getLocale } from "next-intlayer/server";
|
|
1541
|
+
|
|
1542
|
+
export const myServerAction = async () => {
|
|
1543
|
+
const locale = await getLocale();
|
|
1544
|
+
|
|
1545
|
+
// Faça algo com o locale
|
|
1546
|
+
};
|
|
1547
|
+
```
|
|
1548
|
+
|
|
1549
|
+
> A função `getLocale` segue uma estratégia em cascata para determinar o locale do usuário:
|
|
1550
|
+
>
|
|
1551
|
+
> 1. Primeiro, verifica os cabeçalhos da requisição para um valor de localidade que pode ter sido definido pelo proxy
|
|
1552
|
+
> 2. Se nenhuma localidade for encontrada nos cabeçalhos, procura uma localidade armazenada nos cookies
|
|
1553
|
+
> 3. Se nenhum cookie for encontrado, tenta detectar a língua preferida do usuário a partir das configurações do navegador
|
|
1554
|
+
> 4. Como último recurso, recorre à localidade padrão configurada na aplicação
|
|
1555
|
+
>
|
|
1556
|
+
> Isso garante que a localidade mais apropriada seja selecionada com base no contexto disponível.
|
|
1557
|
+
|
|
1558
|
+
### (Opcional) Passo 13: Otimize o tamanho do seu bundle
|
|
1559
|
+
|
|
1560
|
+
Ao usar `next-intlayer`, os dicionários são incluídos no bundle para cada página por padrão. Para otimizar o tamanho do bundle, o Intlayer fornece um plugin SWC opcional que substitui inteligentemente as chamadas `useIntlayer` usando macros. Isso garante que os dicionários sejam incluídos apenas nos bundles das páginas que realmente os utilizam.
|
|
1561
|
+
|
|
1562
|
+
Para ativar essa otimização, instale o pacote `@intlayer/swc`. Uma vez instalado, o `next-intlayer` detectará e usará automaticamente o plugin:
|
|
1563
|
+
|
|
1564
|
+
```bash packageManager="npm"
|
|
1565
|
+
npm install @intlayer/swc --save-dev
|
|
1566
|
+
```
|
|
1567
|
+
|
|
1568
|
+
```bash packageManager="pnpm"
|
|
1569
|
+
pnpm add @intlayer/swc --save-dev
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
```bash packageManager="yarn"
|
|
1573
|
+
yarn add @intlayer/swc --save-dev
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
> Nota: Esta otimização está disponível apenas para Next.js 13 ou superior.
|
|
1577
|
+
|
|
1578
|
+
> Nota: Este pacote não é instalado por padrão porque os plugins SWC ainda são experimentais no Next.js. Isso pode mudar no futuro.
|
|
1579
|
+
|
|
1580
|
+
### Monitorar alterações nos dicionários com Turbopack
|
|
1581
|
+
|
|
1582
|
+
Ao usar o Turbopack como seu servidor de desenvolvimento com o comando `next dev`, as alterações nos dicionários não serão detectadas automaticamente por padrão.
|
|
1583
|
+
|
|
1584
|
+
Essa limitação ocorre porque o Turbopack não pode executar plugins webpack em paralelo para monitorar alterações nos seus arquivos de conteúdo. Para contornar isso, você precisará usar o comando `intlayer watch` para executar simultaneamente o servidor de desenvolvimento e o observador de build do Intlayer.
|
|
1585
|
+
|
|
1586
|
+
```json5 fileName="package.json"
|
|
1587
|
+
{
|
|
1588
|
+
// ... Suas configurações existentes do package.json
|
|
1589
|
+
"scripts": {
|
|
1590
|
+
// ... Suas configurações existentes de scripts
|
|
1591
|
+
"dev": "intlayer watch --with 'next dev'",
|
|
1592
|
+
},
|
|
1593
|
+
}
|
|
1594
|
+
```
|
|
1595
|
+
|
|
1596
|
+
> Se você estiver usando next-intlayer@<=6.x.x, precisa manter a flag `--turbopack` para que a aplicação Next.js 16 funcione corretamente com o Turbopack. Recomendamos usar next-intlayer@>=7.x.x para evitar essa limitação.
|
|
1597
|
+
|
|
1598
|
+
### Configurar TypeScript
|
|
1599
|
+
|
|
1600
|
+
O Intlayer usa a ampliação de módulos para aproveitar os benefícios do TypeScript e tornar sua base de código mais robusta.
|
|
1601
|
+
|
|
1602
|
+

|
|
1603
|
+
|
|
1604
|
+

|
|
1605
|
+
|
|
1606
|
+
Certifique-se de que sua configuração do TypeScript inclua os tipos gerados automaticamente.
|
|
1607
|
+
|
|
1608
|
+
```json5 fileName="tsconfig.json"
|
|
1609
|
+
{
|
|
1610
|
+
// ... Suas configurações existentes do TypeScript
|
|
1611
|
+
"include": [
|
|
1612
|
+
// ... Suas configurações existentes do TypeScript
|
|
1613
|
+
".intlayer/**/*.ts", // Inclua os tipos gerados automaticamente
|
|
1614
|
+
],
|
|
1615
|
+
}
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
### Configuração do Git
|
|
1619
|
+
|
|
1620
|
+
É recomendado ignorar os arquivos gerados pelo Intlayer. Isso permite evitar que eles sejam commitados no seu repositório Git.
|
|
1621
|
+
|
|
1622
|
+
Para isso, você pode adicionar as seguintes instruções ao seu arquivo `.gitignore`:
|
|
1623
|
+
|
|
1624
|
+
```plaintext fileName=".gitignore"
|
|
1625
|
+
# Ignore os arquivos gerados pelo Intlayer
|
|
1626
|
+
.intlayer
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
### Extensão VS Code
|
|
1630
|
+
|
|
1631
|
+
Para melhorar sua experiência de desenvolvimento com o Intlayer, você pode instalar a extensão oficial **Intlayer VS Code Extension**.
|
|
1632
|
+
|
|
1633
|
+
[Instalar no VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=intlayer.intlayer-vs-code-extension)
|
|
1634
|
+
|
|
1635
|
+
Esta extensão oferece:
|
|
1636
|
+
|
|
1637
|
+
- **Autocompletar** para chaves de tradução.
|
|
1638
|
+
- **Detecção de erros em tempo real** para traduções ausentes.
|
|
1639
|
+
- **Visualizações inline** do conteúdo traduzido.
|
|
1640
|
+
- **Ações rápidas** para criar e atualizar traduções facilmente.
|
|
1641
|
+
|
|
1642
|
+
Para mais detalhes sobre como usar a extensão, consulte a [documentação da Extensão Intlayer para VS Code](https://intlayer.org/doc/vs-code-extension).
|
|
1643
|
+
|
|
1644
|
+
### Ir Além
|
|
1645
|
+
|
|
1646
|
+
Para ir além, você pode implementar o [editor visual](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/intlayer_visual_editor.md) ou externalizar seu conteúdo usando o [CMS](https://github.com/aymericzip/intlayer/blob/main/docs/docs/pt/intlayer_CMS.md).
|