Devido a mudanças de foco e prioridades, não podemos mais manter este pacote. Ele não receberá atualizações, correções de bugs ou novos recursos e poderá se tornar incompatível com o tempo. Recomendamos mudar para pacotes que suportam o diretório de aplicativos Next.js mais recente.
next-multilingual é uma solução de ponta a ponta opinativa para os aplicativos Next.JS ✱ que requer vários idiomas.
Confira nosso aplicativo de demonstração!
✱ Com o qual next-multilingual funciona apenas? diretório pages . Ainda estamos resolvendo nossa solução para apoiar o novo? Diretório de app Como a internacionalização não faz mais parte do Next.JS 'Config.
npm install next-multilingual
useMessages que suporta a ICU MessageFormat e JSX injeção pronta para fora da caixa./en-us/contact-us para nós ingleses e /fr-ca/nous-joindre dos EUA para o francês canadense) ✱ . ✱ Suas lesmas de localidade padrão devem corresponder ao sistema de arquivos do diretório pages (por exemplo, uma lesma para "Sobre nós" deve estar em um diretório about-us ). Se o local padrão que você precisar de usar os caracteres além dos suportados pelo sistema de arquivos, ele não foi testado e provavelmente não funcionará. Os pedidos de tração são bem -vindos?
next-multilingual se esforçou muito para adicionar o TSDOC a todas as suas APIs. Verifique diretamente o seu IDE se não tiver certeza de como usar certas APIs fornecidas em nossos exemplos.
Além disso, ter uma opinião sobre "melhores práticas" não é uma tarefa fácil. É por isso que documentamos nossas decisões de design em um documento especial que pode ser consultado aqui. Se você sentir que algumas de nossas APIs não oferecem o que você esperaria, consulte este documento antes de abrir um problema.
Para aqueles que preferem pular direto na ação, procure no diretório example para uma implementação de ponta a ponta do next-multilingual . Para restos, a seção abaixo fornecerá um guia de configuração completo e passo a passo.
Existem muitas opções para configurar no Next.js para atingir nossos objetivos. next-multilingual se importa principalmente com:
Oferecemos duas APIs para simplificar esta etapa:
getConfig (configuração simples) Esta função gerará uma configuração Next.js que atenderá à maioria dos casos de uso. getConfig leva os seguintes argumentos:
applicationId - o identificador de aplicativo exclusivo que será usado como uma chave de mensagens prefixo.locales - os locais do seu aplicativo.defaultLocale - O local padrão do seu aplicativo (também deve ser incluído nos locales )❗ Somente as tags de idioma BCP 47 seguem o formato do
language-countrysão aceitas. Para obter mais detalhes sobre o porquê, consulte o documento de decisões de design.
options (Opcional) - Opções parte de um objeto de configuração Next.js. getConfig retornará um objeto de configuração Next.js.
Para usá -lo, basta adicionar o seguinte código no next.config.js :
const { getConfig } = require ( 'next-multilingual/config' )
const config = getConfig ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' , {
// Put your optional options below.
poweredByHeader : false ,
} )
module . exports = config Nem todas as opções de configuração não são suportadas pelo getConfig . Se você já usar um, uma mensagem de erro o apontará diretamente para a próxima seção: Config avançado.
Config (Config Advanced) Se você tiver necessidades mais avançadas, poderá usar o objeto Config diretamente e inserir a configuração exigida pelo next-multilingual diretamente em um next.config.js existente. Os argumentos da Config são quase idênticos ao getConfig (menos as options ) - Verifique o seu IDE (TSDOC) para obter detalhes. Aqui está um exemplo de como ele pode ser usado:
const { Config , webpackConfigurationHandler } = require ( 'next-multilingual/config' )
const config = new Config ( 'exampleApp' , [ 'en-US' , 'fr-CA' ] , 'en-US' )
module . exports = {
reactStrictMode : true ,
i18n : {
locales : config . getUrlLocalePrefixes ( ) ,
defaultLocale : config . getDefaultUrlLocalePrefix ( ) ,
localeDetection : false ,
} ,
poweredByHeader : false ,
webpack : webpackConfigurationHandler ,
}Se você precisar personalizar sua própria configuração do WebPack, recomendamos estender nosso manipulador como este:
import Webpack from 'webpack'
import { webpackConfigurationHandler , WebpackContext } from 'next-multilingual/config'
export const myWebpackConfigurationHandler = (
config : Webpack . Configuration ,
context : WebpackContext
) : Webpack . Configuration => {
const myConfig = webpackConfigurationHandler ( config , context )
// Do stuff here.
return myConfig
} Ou diretamente em next.config.js :
// Webpack handler wrapping next-multilingual's handler.
const webpack = ( config , context ) => {
config = webpackConfigurationHandler ( config , context )
// Do stuff here.
return config
} next-multilingual/config faz 2 coisas que aproveitam o recurso de roteamento atual do Next.JS:
next-multilingual/config também lida com a configuração especial do WebPack necessária para a renderização do lado do servidor de URLs localizados usando next-multilingual/link/ssr para componentes Link e next-multilingual/head/ssr para links canônicos e alternativos no componente Head .
Para obter mais detalhes sobre a implementação, como por que estamos usando caracteres UTF-8, consulte o documento de decisões de design.
next-multilingual/messages/babel-plugin Para exibir mensagens localizadas com o gancho useMessages() , precisamos configurar nosso plug -in Babel personalizado que injetará automaticamente strings em páginas e componentes. A maneira recomendada de fazer isso é incluir um .babelrc na base do seu aplicativo:
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
} Se você não configurar o plug -in, receberá um erro ao tentar usar useMessages .
App personalizado ( _app.tsx ) Precisamos criar um App personalizado adicionando _app.tsx no diretório pages :
import { useActualLocale } from 'next-multilingual'
import type { AppProps } from 'next/app'
const ExampleApp : React . FC < AppProps > = ( { Component , pageProps } ) => {
useActualLocale ( ) // Forces Next.js to use the actual (proper) locale.
return < Component { ... pageProps } / >
}
export default ExampleAppIsso basicamente faz duas coisas, como mencionado nos comentários:
/ ). Se você não deseja usar a detecção de localidade do next-multilingual pode usar useActualLocale(false) .Document personalizado ( _document.tsx ) Também precisamos criar um Document personalizado adicionando _document.tsx no diretório pages :
import { getHtmlLang } from 'next-multilingual'
import { DocumentProps , Head , Html , Main , NextScript } from 'next/document'
const Document : React . FC < DocumentProps > = ( documentProps ) => {
return (
< Html lang = { getHtmlLang ( documentProps ) } translate = "no" className = "notranslate" >
< Head >
< meta name = "google" content = "notranslate" / >
< / Head >
< body >
< Main / >
< NextScript / >
< / body >
< / Html >
)
}
export default Document Isso serve apenas 1 propósito: exiba a localidade do lado do servidor correta na tag <html> . Como estamos usando um local padrão "falso", é importante manter a marcação SSR correta, especialmente ao resolver um local dinâmico em / .
next-multilingual/head fornece um componente <Head> que cria automaticamente um link canônico e links alternativos no cabeçalho. Isso é algo que não é fornecido fora da caixa por Next.JS.
NEXT_PUBLIC_ORIGIN De acordo com o Google, os links alternativos devem ser totalmente qualificados, incluindo o método de transporte (HTTP/HTTPS). Como o próximo.js não sabe qual URL é usado no horário de construção, precisamos especificar o URL absoluto que será usado, em uma variável de ambiente. Por exemplo, para o ambiente de desenvolvimento, crie um arquivo .env.development na raiz do seu aplicativo com a seguinte variável (ajuste com base na sua configuração):
NEXT_PUBLIC_ORIGIN =http://localhost:3000 Independentemente do ambiente, next-multilingual procurará uma variável chamada NEXT_PUBLIC_ORIGIN para gerar URLs totalmente qualificados. Se você estiver usando o Next.js ' basePath , ele será adicionado automaticamente ao URL base.
NEXT_PUBLIC_ORIGIN aceitará apenas domínios totalmente qualificados (por exemplo, http://example.com ), sem caminhos.
next-multilingual ? Agora que tudo foi configurado, podemos nos concentrar em usar next-multilingual !
Para fazer com que next-multilingual funcione como projetado, tivemos que encontrar soluções para 2 problemas:
undefined : como os sites de suporte do Next.Js sem locais, seus tipos nativos permitem ter valores undefined , o que para o nosso caso é mais um aborrecimento e requer fundição extra.next-multilingual precisa criar um local padrão que nunca usamos. Isso significa que, para acessar as informações relevantes sobre o local, não podemos confiar nas APIs do Next.js.Criamos as seguintes APIs para permitir valores de locais consistentes em seu aplicativo:
useRouter Este é um invólucro simples no topo do próximo.js ' useRouter , que oferece os locais corretos, mas também nunca retorna undefined .
import { NextPage } from 'next'
import { useRouter } from 'next-multilingual/router'
const Page : NextPage = ( ) => {
const router = useRouter ( )
return < > { router . locale } < / >
}
export default Page getStaticPropsLocales import { getStaticPropsLocales } from 'next-multilingual'
export const getStaticProps : GetStaticProps = async ( context ) => {
const { locale , locales , defaultLocale } = getStaticPropsLocales ( context )
// do stuff
return { props : { } }
} getStaticPathsLocales import { getStaticPathsLocales } from 'next-multilingual'
export const getStaticPaths : GetStaticProps = async ( context ) => {
const { locales , defaultLocale } = getStaticPathsLocales ( context )
// do stuff
return { props : { } }
} getServerSidePropsLocales import { getServerSidePropsLocales } from 'next-multilingual'
export const getServerSideProps : GetServerSideProps = async ( context ) => {
const { locale , locales , defaultLocale } = getServerSidePropsLocales ( context )
// do stuff
return { props : { } }
}
️ Observe que, embora recomendamos o uso de detecção de localidade inteligente para renderizar dinamicamente a página inicial, isso é completamente opcional. Usando a configuração avançada comlocaleDetection: true, você restaurará o comportamento padrão do próximo.js sem a necessidade de usargetServerSideProps.
A página inicial é um pouco mais complexa do que outras páginas, porque precisamos implementar a detecção dinâmica de localidade (e exibição) pelo seguinte motivo:
/ pode ter um impacto negativo no SEO e não é a melhor experiência do usuário.next-multilingual vem com uma API getPreferredLocale que oferece detecção automática mais inteligente do que a implementação padrão Next.js.Você pode encontrar uma implementação completa no exemplo, mas aqui está uma versão despojada:
import type { GetServerSideProps , NextPage } from 'next'
import { ResolvedLocaleServerSideProps , resolveLocale , useResolvedLocale } from 'next-multilingual'
import { useRouter } from 'next-multilingual/router'
const Homepage : NextPage < ResolvedLocaleServerSideProps > = ( { resolvedLocale } ) => {
// Force Next.js to use a locale that was resolved dynamically on the homepage (this must be the first action on the homepage).
useResolvedLocale ( resolvedLocale )
const { locale } = useRouter ( )
return < h1 > { locale } </ h1 >
}
export default Homepage
export const getServerSideProps : GetServerSideProps < ResolvedLocaleServerSideProps > = async (
context
) => {
return {
props : {
resolvedLocale : resolveLocale ( context ) ,
} ,
}
}Em poucas palavras, é isso que está acontecendo:
next-multilingual .useResolvedLocale para tornar essa dinâmica em todo o aplicativo. Toda vez que você cria um arquivo tsx , ts , jsx ou js (compilável) e que você precisa de mensagens localizadas, basta criar um arquivo de mensagem em seus locais suportados que só serão utilizáveis por esses arquivos. Assim como os módulos CSS, a idéia é que você possa ter arquivos de mensagem associados ao escopo local de outro arquivo. Isso tem o benefício de tornar as mensagens mais modulares e também evita o compartilhamento de mensagens em diferentes contextos (mais detalhes no documento de decisões de design sobre por que isso é ruim).
Os arquivos de mensagens têm 2 casos de uso principal:
pages , você pode especificar um segmento de URL localizado (parte de um URL entre / ou no final do caminho) usando o identificador da chave slug . Mais detalhes sobre como fazer isso abaixo.useMessages . Imagine CSS, mas para cordas localizáveis.Para resumir:
Criar e gerenciar esses arquivos é tão simples quanto criar uma folha de estilo, mas aqui estão os detalhes importantes:
.properties . Sim, você pode se perguntar por que, mas há boas razões documentadas no documento de decisão de design.UTF-8 . Não fazer isso substituirá caracteres não latinos por �.properties , seguimos uma convenção de nomeação estrita: <PageFilename>.<locale>.properties<applicationId>.<context>.<id>next-multilingual/configaboutUsPage ou footerComponent pode ser um bom exemplo de contexto. Cada arquivo pode conter apenas 1 contexto e contexto não deve ser usado em muitos arquivos, pois isso pode causar "colisão chave" (teclas não únicas).. e só pode conter entre 1 a 50 caracteres alfanuméricos - recomendamos o uso da caixa de camelo para obter legibilidade.slug .title .getTitle fornecida em next-multilingual/messages para automaticamente entre as teclas title e slug .useMessages .Além disso, verifique o log do seu console em busca de avisos sobre possíveis problemas com suas mensagens. Pode ser complicado se acostumar com a forma como funciona primeiro, mas tentamos facilitar a detecção e corrigir problemas. Observe que esses logs serão exibidos apenas em ambientes de não produção.
Não é incomum precisar de mensagens localizadas enquanto não pode usar ganchos. Um exemplo seria o uso de um dos principais recursos do Next.JS é o suporte da API incorporado. Nesse contexto, em vez de usar useMessage , podemos simplesmente usar getMessages enquanto especifica o argumento locale .
Como mencionado anteriormente, há uma chave especial para pages , onde o id é slug . Ao contrário das lesmas tradicionais que se parecem com this-is-a-page , pedimos que você escreva a lesma como uma frase legível normal e humana, para que ela possa ser traduzida como qualquer outra corda. Isso evita ter processos especiais para lesmas que podem ser caras e complexas para gerenciar em vários idiomas.
Basicamente, a slug é a "descrição curta" legível humana da sua página e representa um segmento (parte entre / ou no final do caminho) de um URL. Quando usado como um segmento de URL, a seguinte transformação é aplicada:
- Por exemplo, About Us se tornará about-us .
Para a página inicial, o URL sempre será / o que significa que as teclas slug não serão usadas para criar segmentos de URL localizados.
Não se esqueça, as lesmas devem ser escritas como uma descrição curta normal, o que significa pular palavras para mantê -lo mais curto para o SEO é desencorajado. A principal razão para isso é que, se você escrever "um monte de palavras -chave", um linguista que não está familiarizado com o SEO pode ter dificuldade em traduzir essa mensagem. Ter especialistas em SEO em vários idiomas também seria muito caro e difícil de escalar. Em um cenário ideal, as páginas de SEO específicas do mercado provavelmente devem ser de autoria e otimizadas nos idiomas nativos, mas isso não faz mais parte do processo de tradução. O foco do next-multilingual é fornecer uma solução fácil e simplificada para localizar URLs em vários idiomas.
A tecla slug também será usada como um fallback da tecla title ao usar a API getTitle fornecida em next-multilingual/messages . Esta API facilita a personalização de títulos quando uma lesma parece insuficiente.
️ Observe que alterar um valorslugsignifica que um URL mudará. Como essas alterações estão acontecendo nonext.config.js, como qualquer alteração do Next.js Config, o servidor deve ser reiniciado para ver as alterações em vigor. O mesmo se aplica se você alterar a estrutura da pasta, pois a configuração subjacente depende disso.
Se você deseja ter um diretório sem páginas, ainda pode localizá -lo criando locale index.<locale>.properties Embora essa opção seja suportada, não recomendamos usá -la, pois isso tornará os caminhos de URL mais longos, o que vai contra as melhores práticas de SEO.
Por padrão, next-multilingual excluirá alguns arquivos como páginas de erro personalizadas ou quaisquer rotas de API no diretório /api . Você sempre pode usar teclas slug ao usar mensagens para esses arquivos, mas elas não serão usadas para criar URLs localizados.
Você sempre pode analisar o exemplo para ver arquivos de mensagem em ação, mas aqui está uma amostra que pode ser usada na página inicial:
# Homepage title
exampleApp.homepage.title = Homepage
# Homepage headline
exampleApp.homepage.headline = Welcome to the homepage Agora que aprendemos a criar a página inicial e alguns dos detalhes sobre como as coisas funcionam, podemos criar facilmente outras páginas. Criamos muitas páginas no exemplo, mas aqui está uma amostra de about-us.jsx se pode parecer:
import { NextPage } from 'next'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
import styles from './index.module.css'
const AboutUs : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 className = { styles . headline } > { title } </ h1 >
< p > { messages . format ( 'details' ) } </ p >
</ Layout >
)
}
export default AboutUs E é claro que você teria este arquivo de mensagem about-us.en-US.properties :
# Page localized URL segment (slug) in (translatable) human readable format.
# This key will be "slugified" (e.g, "About Us" will become "about-us"). All non-alphanumeric characters will be replaced by "-".
exampleApp.aboutUsPage.slug = About Us
# Page details.
exampleApp.aboutUsPage.details = This is just some english boilerplate text. next-multilingual vem com seu próprio componente <Link> que permite a renderização do lado do cliente e do servidor de URL localizado. Seu uso é simples, funciona exatamente como o próximo.js ' <Link> .
A única coisa importante a lembrar é que o atributo href deve sempre conter o próximo.js URL. Ou seja, a estrutura do arquivo na pasta pages deve ser o que é usado e não as versões localizadas.
Em outras palavras, a estrutura do arquivo é considerada como a representação de URL "não localizada" e <Link> cuidará de substituir os URLs pelas versões localizadas (dos arquivos das mensagens), se diferirem da estrutura.
A API está disponível em next-multilingual/link e você pode usá-lo assim:
import Link from 'next-multilingual/link'
import { useMessages } from 'next-multilingual/messages'
const Menu : React . FC = ( ) => {
const messages = useMessages ( )
return (
< nav >
< Link href = "/" >
< a > { messages . format ( 'home' ) } </ a >
</ Link >
< Link href = "/about-us" >
< a > { messages . format ( 'aboutUs' ) } </ a >
</ Link >
< Link href = "/contact-us" >
< a > { messages . format ( 'contactUs' ) } </ a >
</ Link >
</ nav >
)
}
export default Menu Cada um desses links será localizado automaticamente quando a tecla slug for especificada no arquivo de mensagem dessa página. Por exemplo, em nós, inglês, o caminho da URL "Entre em contato" será /en-us/contact-us enquanto no francês canadense será /fr-ca/nous-joindre .
Como os dados para esse mapeamento não estão disponíveis imediatamente durante a renderização, next-multilingual/link/ssr cuidará da renderização do lado do servidor (SSR). Ao usar getConfig da next-multilingual/config , a configuração do WebPack será adicionada automaticamente. Se você estiver usando o método Config avançada, isso explica por que a configuração especial do WebPack é necessária no exemplo fornecido antes.
Nem todos os URLs localizados estão usando o componente <Link> e é também por isso que o Next.js possui o método do router.push que pode ser usado por muitos outros casos de uso. next-multilingual pode suportar esses casos de uso com o gancho useLocalizedUrl que retornará um URL localizado, utilizável por quaisquer componentes. Aqui está um exemplo de como ele pode ser alavancado:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const localizedUrl = useLocalizedUrl ( '/about-us' )
return < button onClick = { ( ) => router . push ( localizedUrl ) } > { messages . format ( 'clickMe' ) } </ button >
}
export default Tests Se você preferir definir seus URLs embutidos em vez de no topo do componente, ou se precisar fazer manipulações de URL mais avançadas, também poderá usar o gancho useGetLocalizedUrl que retorna uma função para obter URLs:
import { NextPage } from 'next'
import { useMessages } from 'next-multilingual/messages'
import { useGetLocalizedUrl } from 'next-multilingual/url'
import router from 'next/router'
const Tests : NextPage = ( ) => {
const messages = useMessages ( )
const { getLocalizedUrl } = useGetLocalizedUrl ( )
return (
< button onClick = { ( ) => router . push ( getLocalizedUrl ( '/about-us' ) ) } >
{ messages . format ( 'clickMe' ) }
</ button >
)
}
export default TestsTenha cuidado, se você deseja usar o valor da string do URL dentro dos elementos do React, você terá erros porque os URLs diferem entre a pré-renderização e o navegador. A razão para isso é que, no cliente, na primeira renderização, o Next.js não tem acesso aos dados de reescrita e, portanto, usa caminhos de URL "semi-localizados" (por exemplo
/fr-ca/about-us). Como este é um comportamento nativo do próximo.js, a maneira mais simples de contornar isso por enquanto é adicionarsuppressHydrationWarning={true}ao seu elemento. Para contornar isso,useGetLocalizedUrltambém retorna uma propriedadeisLoadingque pode ser usada para rastrear quando os URLs localizados estiverem disponíveis para uso no cliente.
Você pode encontrar uma situação em que também precisa obter um URL localizado, mas usar um gancho não é uma opção. É aqui que entra getLocalizedUrl no next-multilingual/url . Ele age da mesma forma que useLocalizedUrl , mas seu argumento locale é obrigatório.
Imagine o uso da API do Next.JS para enviar e-mails transacionais e desejar aproveitar os URLs localizados do next-multilingual sem precisar codificá-los em uma configuração. Aqui está um exemplo de como ele pode ser usado:
import type { NextApiRequest , NextApiResponse } from 'next'
import { isLocale } from 'next-multilingual'
import { getMessages } from 'next-multilingual/messages'
import { getLocalizedUrl } from 'next-multilingual/url'
import { sendEmail } from 'send-email'
/**
* The "/api/send-email" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse ) : Promise < void > => {
const locale = request . headers [ 'accept-language' ]
let emailAddress = ''
try {
emailAddress = JSON . parse ( request . body ) . emailAddress
} catch ( error ) {
response . status ( 400 )
return
}
if ( locale === undefined || ! isLocale ( locale ) || ! emailAddress . length ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
sendEmail (
emailAddress ,
messages . format ( 'welcome' , { loginUrl : await getLocalizedUrl ( '/login' , locale , true ) } )
)
response . status ( 200 )
}
export default handler Criar componentes é o mesmo que páginas, mas eles vivem fora do diretório pages . Além disso, a chave slug (se usada) não terá nenhum impacto nos URLs. Temos alguns componentes de exemplo que devem ser auto-explicativos, mas aqui está um exemplo de Footer.tsx .
import { useMessages } from 'next-multilingual/messages'
const Footer : React . FC = ( ) => {
const messages = useMessages ( )
return < footer > { messages . format ( 'footerMessage' ) } </ footer >
}
export default FooterE seu arquivo de mensagens:
# This is the message in the footer at the bottom of pages
exampleApp.footerComponent.footerMessage = © FooterAlém disso, verifique o exemplo do componente do comutador de idiomas que é obrigatório em todos os aplicativos multilíngues.
Ficamos claros que o compartilhamento de mensagens é uma prática ruim desde o início, então do que estamos falando aqui? De fato, compartilhar mensagens por si só não é ruim. O que pode causar problemas é quando você compartilha mensagens em diferentes contextos. Por noButton , você pode ser tentado a criar um Button.ts yesButton Em muitos idiomas, palavras simples, como "Sim" e "Não", podem ter grafias diferentes, dependendo do contexto, mesmo que seja um botão.
Quando é bom compartilhar mensagens? Para listas de itens.
Por exemplo, para manter seu processo de localização simples, você deseja evitar armazenar strings localizáveis em seu banco de dados (mais detalhes sobre por que no documento de decisão de design). No seu banco de dados, você identificaria o contexto usando identificadores exclusivos e armazenaria suas mensagens em arquivos de mensagens compartilhados, onde os identificadores da sua chave corresponderiam aos do banco de dados.
Para ilustrar isso, criamos um exemplo usando frutas. Tudo o que você precisa fazer é criar um gancho que chama useMessages como esta:
export { useMessages as useFruitsMessages } from 'next-multilingual/messages'❗ Se você precisar acessar suas mensagens fora dos ganchos, também precisará exportar
getMessages.
Obviamente, você terá seus arquivos de mensagens no mesmo diretório:
exampleApp.fruits.banana = Banana
exampleApp.fruits.apple = Apple
exampleApp.fruits.strawberry = Strawberry
exampleApp.fruits.grape = Grape
exampleApp.fruits.orange = Orange
exampleApp.fruits.watermelon = Watermelon
exampleApp.fruits.blueberry = Blueberry
exampleApp.fruits.lemon = LemonE para usá -lo, importe esse gancho simples de qualquer lugar que você precise desses valores:
import { useFruitsMessages } from '../messages/useFruitsMessages'
const FruitList : React . FC ( ) => {
const fruitsMessages = useFruitsMessages ( )
return (
< >
{ fruitsMessages
. getAll ( )
. map ( ( message ) => message . format ( ) )
. join ( ', ' ) }
</ >
)
}
export default FruitListVocê também pode ligar para mensagens individuais como esta:
fruitsMessages . format ( 'banana' )A idéia de compartilhar essas listas de itens é que você pode ter uma experiência consistente em diferentes componentes. Imagine um suspensão com uma lista de frutas em uma página e em outra página uma entrada completa automaticamente. Mas a parte importante a lembrar é que a lista deve sempre ser usada no mesmo contexto, para não reutilizar algumas das mensagens em um contexto diferente.
O uso de espaços reservados nas mensagens é uma funcionalidade crítica, pois nem todas as mensagens contêm texto estático. next-multilingual suporta a sintaxe da UTI MessageFormat pronta para a caixa, o que significa que você pode usar a seguinte mensagem:
exampleApp.homepage.welcome = Hello {name}!E injete os valores usando:
messages . format ( 'welcome' , { name : 'John Doe' } ) format Existem algumas regras simples a serem lembradas ao usar format :
values ao formatar a mensagem, ele simplesmente produzirá a mensagem como texto estático.values ao formatar a mensagem, deverá incluir os valores de todos os espaços reservados usando a sintaxe {placeholder} em sua mensagem. Caso contrário, a mensagem não será exibida.values que não estiverem em sua mensagem, eles serão silenciosamente ignorados. Um dos principais benefícios do UTIM MessageFormat é usar as ferramentas e padrões da Unicode para permitir que os aplicativos pareçam fluentes na maioria dos idiomas. Muitos engenheiros podem acreditar que, com 2 mensagens, uma para singular e outra para plural é suficiente para permanecer fluente em todos os idiomas. De fato, o Unicode documentou as regras plurais de mais de 200 idiomas e alguns idiomas como o árabe podem ter até 6 formas plurais.
Para garantir que sua frase permaneça fluente em todos os idiomas, você pode usar a seguinte mensagem:
exampleApp.homepage.mfPlural = {count, plural, =0 {No candy left.} one {Got # candy left.} other {Got # candies left.}}E o formulário plural correto será escolhido, usando as categorias plurais corretas definidas pelo Unicode:
messages . format ( 'mfPlural' , { count } )Há muito a aprender sobre esse tópico. Leia a documentação do Unicode e tente a sintaxe para se familiarizar com esse recurso I18N sub-hyped.
Em um evento raro em que você precisaria usar os dois espaços reservados usando a sintaxe {placeholder} e também exibir os caracteres { e } em uma mensagem, você precisará substituí -los pelo { (para { ) e } (para } ) entidades html que são reconhecidas por ferramentas de tradução como esta:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}} Se você tiver uma mensagem sem valores (espaço reservado), não é necessário escapar { e } com entidades HTML e exibirá entidades como texto estático.
É uma situação muito comum que precisamos ter HTML embutido, dentro de uma única mensagem. Uma maneira de fazer isso seria:
# Bad example, do not ever do this!
exampleApp.homepage.createAccount1 = Please
exampleApp.homepage.createAccount2 = create your account
exampleApp.homepage.createAccount3 = today for free.E então:
< div >
{ messages . format ( 'createAccount1' ) }
< Link href = "/sign-up" > { messages . format ( 'createAccount2' ) } </ Link >
{ messages . format ( 'createAccount3' ) }
</ div >Existem 2 problemas com esta abordagem:
Na verdade, isso é um anti-padrão chamado concatenação e deve sempre ser evitado. Esta é a maneira correta de fazer isso, usando formatJsx :
exampleApp.homepage.createAccount = Please <link>create your account</link> today for free.E então:
< div > { messages . formatJsx ( 'createAccount' , { link : < Link href = "/sign-up" > </ Link > } ) } </ div > formatJsx formatJsx suporta os elementos de espaço reservado e JSX como values o que significa que você pode se beneficiar dos recursos format padrão (por exemplo, plurais) enquanto injeta elementos JSX.
Existem algumas regras simples a serem lembradas ao usar format :
formatJsx .<link> xml, o elemento JSX precisa ser fornecido usando link: <Link href="/"></Link> .<i> muitas vezes em uma frase, precisará criar tags exclusivas como <i1> , <i2> etc. e passar seus valores no argumento como elementos JSX.Hello <name>{name}</name> ) não é suportado..properties .<Link href="/contact-us><a id="test"></a></Link> é válido, mas <div><span1></span1><span2></span2></div> é inválido. Em vez disso, você deve usar a marcação XML do mesmo nível no arquivo .properties e não como Jsx. > < Ao usar formatJsx você ainda precisará escapar de suportes encaracolados se quiser exibi -los como texto. Além disso, como usaremos o XML nas mensagens formatJsx , regras > serão aplicadas a < usadas para identificar tags.
Em um evento raro > em que você precisaria injetar JSX em uma mensagem usando a sintaxe <element></element> (xml) e também exibir os caracteres < em uma mensagem, você precisará substituí -los pelo < (para < ) e > (para > ) entidades HTML que são reconhecidas por ferramentas de tradução como esta:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.Os links de âncora são links que levam você a um local específico em um documento, e não no topo.
Um dos principais recursos do principal next-multilingual é suportar URLs localizados. Nosso design foi construído usando frases normais que são fáceis de localizar e depois transformadas em lesmas amigáveis para SEO. Podemos usar a mesma função para lidar com links de âncora, para que, em vez de ter /fr-ca/nous-joindre#our-team você pode ter /fr-ca/nous-joindre#notre-équipe .
Existem dois tipos de links de âncora:
Se os links âncora estiverem na mesma página e não forem referidos em outras páginas, você poderá simplesmente adicioná -los no arquivo .properties associado a essa página como esta:
# Table of content header
exampleApp.longPage.tableOfContent = Table of Content
# This key will be used both as content and "slugified". Make sure when translating that its value is unique.
exampleApp.longPage.p1Header = Paragraph 1
# "Lorem ipsum" text to make the (long) page scroll
exampleApp.longPage.p1 = Lorem ipsum dolor sit amet... E então a página pode usar a função slugify para vincular ao identificador exclusivo associado ao elemento que você deseja apontar o fragmento de URL para:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
const LongPage : NextPage = ( ) => {
const messages = useMessages ( )
const { locale } = useRouter ( )
return (
< div >
< div >
< h2 > { messages . format ( 'tableOfContent' ) } </ h2 >
< ul >
< li >
< Link href = { `# ${ slugify ( messages . format ( 'p1Header' ) , locale ) } ` } >
{ messages . format ( 'p1Header' ) }
</ Link >
</ li >
</ ul >
</ div >
< div >
< h2 id = { slugify ( messages . format ( 'p1Header' ) , locale ) } > { messages . format ( 'p1Header' ) } </ h2 >
< p > { messages . format ( 'p1' ) } </ p >
</ div >
</ div >
)
}
export default LongPage Também é comum usar links de âncora nas páginas, para que, quando você clica em um link, seu navegador mostre diretamente o conteúdo relevante nessa página. Para fazer isso, você precisa disponibilizar a mensagem da sua página para outras páginas, adicionando esta exportação simples que agirá como "mensagens compartilhadas":
export const useLongPageMessages = useMessagesE então você pode usar este gancho de outra página como esta:
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { slugify , useMessages } from 'next-multilingual/messages'
import { useRouter } from 'next/router'
import { useLongPageMessages } from './long-page'
const AnchorLinks : NextPage = ( ) => {
const messages = useMessages ( )
const { locale , pathname } = useRouter ( )
const longPageMessages = useLongPageMessages ( )
return (
< div >
< div >
< Link
href = { ` ${ pathname } /long-page# ${ slugify ( longPageMessages . format ( 'p3Header' ) , locale ) } ` }
>
{ messages . format ( 'linkAction' ) }
</ Link >
</ div >
</ div >
)
}
export default AnchorLinksEsse padrão também funciona para componentes. O benefício de fazer isso é que, se você excluir ou refatorar a página, os links âncora associados a ela sempre permanecerão na página.
Você pode criar um componente de mensagem compartilhada separado apenas para os links âncora, mas isso quebraria o princípio da proximidade.
Um exemplo completo de links de âncora pode ser encontrado no aplicativo de exemplo.
Um recurso que está ausente no Next.js é gerenciar marcas HTML importantes usadas para SEO. Adicionamos o componente <Head> para lidar com duas tags muito importantes que vivem no html <head> :
<link rel=canonical> ): Isso diz aos mecanismos de pesquisa que a fonte da verdade para a página sendo navegada é esse URL. Muito importante para evitar ser penalizado por conteúdo duplicado, especialmente porque os URLs são insensíveis ao caso, mas o Google os trata como sensível ao caso.<link rel=alternate> ): Isso informa aos mecanismos de pesquisa que a página que está sendo navegada também está disponível em outros idiomas e facilita a rastreamento do site. A API está disponível em next-multilingual/head e você pode importá-la assim:
import Head from 'next-multilingual/head' Assim como <Link> , <Head> deve ser um substituto para o componente Next.js ' <Head> . Em nosso exemplo, estamos usando -o no componente de layout, assim:
< Head >
< title > { title } </ title >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" > </ meta >
</ Head > Tudo isso é inserir os links canônicos e alternativos para que os mecanismos de pesquisa possam rastejar melhor seu aplicativo. Por exemplo, se você estiver na página /en-us/about-us , o seguinte HTML será adicionado automaticamente na sua tag HTML <head> :
< link rel =" canonical " href =" http://localhost:3000/en-us/about-us " />
< link rel =" alternate " href =" http://localhost:3000/en-us/about-us " hreflang =" en-US " />
< link rel =" alternate " href =" http://localhost:3000/fr-ca/%C3%A0-propos-de-nous " hreflang =" fr-CA " /> Para se beneficiar totalmente da marcação de SEO, <Head> deve ser incluído em todas as páginas. Existem várias maneiras de conseguir isso, mas no exemplo, criamos um componente <Layout> que é usado em todas as páginas.
Como a maioria dos sites, você deseja aproveitar o recurso de páginas de erro personalizado do Next.JS. Com useMessages() , é tão fácil quanto criar outras páginas. Por exemplo, para um erro 404 , você pode criar seu 404.tsx :
import { NextPage } from 'next'
import Link from 'next-multilingual/link'
import { getTitle , useMessages } from 'next-multilingual/messages'
import Layout from '@/layout'
const Error404 : NextPage = ( ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
return (
< Layout title = { title } >
< h1 > { title } </ h1 >
< Link href = "/" >
< a > { messages . format ( 'goBack' ) } </ a >
</ Link >
</ Layout >
)
}
export default Error404 E, claro, suas mensagens, por exemplo, 404.en-US.properties :
# Page title
exampleApp.pageNotFoundError.title = 404 - Page Not Found
# Go back link text
exampleApp.pageNotFoundError.goBack = Go back homeAs APIs geralmente precisam ser localizadas. Aqui está um exemplo "Hello API":
import type { NextApiRequest , NextApiResponse } from 'next'
import { getMessages } from 'next-multilingual/messages'
/**
* Example API schema.
*/
type Schema = {
message : string
}
/**
* The "hello API" handler.
*/
const handler = ( request : NextApiRequest , response : NextApiResponse < Schema > ) : void => {
const locale = request . headers [ 'accept-language' ]
if ( locale === undefined || ! isLocale ( locale ) ) {
response . status ( 400 )
return
}
const messages = getMessages ( locale )
response . status ( 200 ) . json ( { message : messages . format ( 'message' ) } )
} Isso é muito semelhante à API implementada no aplicativo de exemplo. Estamos usando o cabeçalho HTTP Accept-Language para informar a API em que localidade queremos que sua resposta seja. Ao contrário do gancho useMessages que tem o contexto da localidade atual, precisamos informar getMessages em qual localidade retornará mensagens.
Os arquivos de mensagens se comportam exatamente da mesma forma que com useMessages você simplesmente precisa criar um ao lado do arquivo da rota da API, no nosso caso hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.Você pode implementar isso em todas as páginas, assim como qualquer outra chamada de API baseada em React, como esta:
const SomePage : NextPage = ( ) => {
const [ apiError , setApiError ] = useState ( null )
const [ isApiLoaded , setApiIsLoaded ] = useState ( false )
const [ apiMessage , setApiMessage ] = useState ( '' )
useEffect ( ( ) => {
setApiIsLoaded ( false )
const requestHeaders : HeadersInit = new Headers ( )
requestHeaders . set ( 'Accept-Language' , normalizeLocale ( router . locale as string ) )
fetch ( '/api/hello' , { headers : requestHeaders } )
. then ( ( result ) => result . json ( ) )
. then (
( result ) => {
setApiIsLoaded ( true )
setApiMessage ( result . message )
} ,
( apiError ) => {
setApiIsLoaded ( true )
setApiError ( apiError )
}
)
} , [ router . locale ] )
const showApiMessage : React . FC = ( ) => {
if ( apiError ) {
return (
< >
{ messages . format ( 'apiError' ) }
{ ( apiError as Error ) . message }
</ >
)
} else if ( ! isApiLoaded ) {
return < > { messages . format ( 'apiLoading' ) } </ >
} else {
return < > { apiMessage } </ >
}
}
return (
< div >
< h2 > { messages . format ( 'apiHeader' ) } </ h2 >
< div > { showApiMessage ( { } ) } </ div >
</ div >
)
} A normalizeLocale não é obrigatória, mas uma convenção recomendada da ISO 3166. Desde que o Next.js usa os locais como prefixos de URL, eles são mais baixos na configuração e podem ser reormalizados conforme necessário.
Rotas As rotas dinâmicas são complexas e localizá -las acrescenta ainda mais complexidade. Verifique se você está familiarizado com a forma como esse recurso do próximo.js funciona antes de tentar adicionar localização.
As rotas dinâmicas são muito comuns e são apoiadas pela caixa por Next.JS. Desde a versão 3.0, next-multilingual fornece os mesmos suportes do Next.js em termos de rotas dinâmicas. Para fazer as rotas dinâmicas funcionarem com next-multilingual temos alguns padrões a seguir:
href do <Link> e o argumento useLocalizedUrl / useGetLocalizedUrl / getLocalizedUrl url aceita apenas URLs de string.<Link> component which accepts a UrlObject , we preferred to streamline our types since urlObject.href can easily be used instead.userRouter().asPath (most common scenario) by providing localized parameters directly in the URL. By using asPath you are using the localized URL which means that the URL you will use will be fully localized.userRouter().pathname is conjunction with hydrateRouteParameters by providing localized parameters. By using pathname you are using the non-localized URL which means that the URL you will use might be a mix of non-localized segments plus the localized parameters. This can be useful in cases where you have nested dynamic routes.We provided several examples of on on to use dynamic routes in our dynamic route test pages.
The main challenge with dynamic routes, is that if the value of the parameter needs to be localized, we need to keep a relation between languages so that we can correctly switch languages. next-multilingual solves this problem with its getLocalizedRouteParameters API that creates a LocalizedRouteParameters object used as a page props. This can work both with getStaticProps and getServerSideProps .
getStaticProps First you need to tell Next.js which predefined paths will be valid by using getStaticPaths (all imports are added in the first example):
import { getCitiesMessages } from '@/messages/cities/citiesMessages'
import { GetStaticPaths } from 'next'
import { slugify } from 'next-multilingual/messages'
export const getStaticPaths : GetStaticPaths = async ( context ) => {
const paths : MultilingualStaticPath [ ] = [ ]
const { locales } = getStaticPathsLocales ( context )
locales . forEach ( ( locale ) => {
const citiesMessages = getCitiesMessages ( locale )
citiesMessages . getAll ( ) . forEach ( ( cityMessage ) => {
paths . push ( {
params : {
city : slugify ( cityMessage . format ( ) , locale ) ,
} ,
locale ,
} )
} )
} )
return {
paths ,
fallback : false ,
}
} Then you have to pre-compute the localized route parameters and return them as props using getStaticProps and getLocalizedRouteParameters :
export type CityPageProps = { localizedRouteParameters : LocalizedRouteParameters }
export const getStaticProps : GetStaticProps < CityPageProps > = async ( context ) => {
const localizedRouteParameters = getLocalizedRouteParameters (
context ,
{
city : getCitiesMessages ,
} ,
import . meta . url
)
return { props : { localizedRouteParameters } }
}If you are using a catch-all dynamic route, you will need to pass your parameters as an array, for each URL segment that you want to support. For example, if you want to support 2 levels:
const localizedRouteParameters = getLocalizedRouteParameters ( context , {
city : [ getCitiesMessages , getCitiesMessages ] ,
} ) Note that since we need to use the getMessages API instead of the useMessages hook, you will also need to export it in the message file:
export {
getMessages as getCitiesMessages ,
useMessages as useCitiesMessages ,
} from 'next-multilingual/messages'Finally you have to pass down your localized route parameters down to your language switcher component when you create your page:
const CityPage : NextPage < CityPageProps > = ( { localizedRouteParameters } ) => {
const messages = useMessages ( )
const title = getTitle ( messages )
const { query } = useRouter ( )
return (
< Layout title = { title } localizedRouteParameters = { localizedRouteParameters } >
< h1 > { query [ 'city' ] } < / h1>
< / Layout>
)
}
export default CityPage The only part missing now is the language switcher which needs to leverage the localized route parameters by using getLanguageSwitcherUrl :
import { normalizeLocale , setCookieLocale } from 'next-multilingual'
import Link from 'next-multilingual/link'
import { KeyValueObject } from 'next-multilingual/messages'
import { LocalizedRouteParameters , useRouter } from 'next-multilingual/router'
import { getLanguageSwitcherUrl } from 'next-multilingual/url'
import { ReactElement } from 'react'
// Locales don't need to be localized.
const localeStrings : KeyValueObject = {
'en-US' : 'English (United States)' ,
'fr-CA' : 'Français (Canada)' ,
}
type LanguageSwitcherProps = {
/** Route parameters, if the page is using a dynamic route. */
localizedRouteParameters ?: LocalizedRouteParameters
}
export const LanguageSwitcher : React . FC < LanguageSwitcherProps > = ( { localizedRouteParameters } ) => {
const router = useRouter ( )
const { pathname , locale : currentLocale , locales , defaultLocale , query } = useRouter ( )
const href = getLanguageSwitcherUrl ( router , localizedRouteParameters )
return (
< div id = "language-switcher" >
< ul >
{ locales
. filter ( ( locale ) => locale !== currentLocale )
. map ( ( locale ) => {
return (
< li key = { locale } >
< Link href = { href } locale = { locale } >
< a
onClick = { ( ) => {
setCookieLocale ( locale )
} }
lang = { normalizeLocale ( locale ) }
>
{ localeStrings [ normalizeLocale ( locale ) ] }
< / a>
< / Link>
< / li >
)
} ) }
< / ul>
< / div >
)
}Check out our fully working examples:
Our ideal translation process is one where you send the modified files to your localization vendor (while working in a branch), and get back the translated files, with the correct locale in the filenames. Once you get the files back you basically submit them back in your branch which means localization becomes an integral part of the development process. Basically, the idea is:
We don't have any "export/import" tool to help as at the time of writing this document.
next-multilingual ? ?️Why did we put so much effort into these details? Because our hypothesis is that it can have a major impact on:
More details can be found on the implementation and design decision in the individual README files of each API and in the documentation directory.