Debido a los cambios en el enfoque y las prioridades, ya no podemos mantener este paquete. No recibirá actualizaciones, correcciones de errores o nuevas funciones y puede ser incompatible con el tiempo. Recomendamos cambiar a paquetes que admitan el último directorio de aplicaciones Next.js.
next-multilingual es una solución obstinada de extremo a extremo para las aplicaciones Next.js ✱ que requiere múltiples idiomas.
¡Mira nuestra aplicación de demostración!
✱ next-multilingual solo funciona con? Directorio pages . ¿Todavía estamos organizando nuestra solución para apoyar la nueva? Directorio app Dado que la internacionalización ya no es parte de la configuración de Next.js.
npm install next-multilingual
useMessages que admite la inyección de mensajes de la UCI y la inyección JSX fuera de la caja./en-us/contact-us para inglés de EE. UU. Y /fr-ca/nous-joindre para francés canadiense) ✱ . ✱ Sus babosas de locales predeterminadas deben coincidir con el sistema de archivos de directorio pages (por ejemplo, una babosa para "Acerca de nosotros" debe estar en un directorio about-us ). Si la localidad predeterminada que necesita usa caracteres más allá de los compatibles con el sistema de archivos, no se ha probado y probablemente no funcionará. ¿Las solicitudes de extracción son bienvenidas?
next-multilingual ha puesto mucho esfuerzo en agregar TSDOC a todas sus API. Consulte directamente en su IDE si no está seguro de cómo usar ciertas API proporcionadas en nuestros ejemplos.
Además, tener una opinión sobre las "mejores prácticas" no es una tarea fácil. Es por eso que documentamos nuestras decisiones de diseño en un documento especial que se puede consultar aquí. Si cree que algunas de nuestras API no ofrecen lo que esperaría, asegúrese de consultar este documento antes de abrir un problema.
Para aquellos que prefieren saltar directamente a la acción, busque en el directorio example para una implementación de extremo a extremo de next-multilingual . Para el resto, la siguiente sección proporcionará una guía de configuración completa y paso a paso.
Hay muchas opciones para configurar en Next.js para lograr nuestros objetivos. next-multilingual se preocupa principalmente por:
Ofrecemos dos API para simplificar este paso:
getConfig (configuración simple) Esta función generará una configuración Next.js que cumplirá con la mayoría de los casos de uso. getConfig toma los siguientes argumentos:
applicationId : el identificador de aplicación único que se utilizará como prefijo de clave de mensajes.locales : los lugares de su aplicación.defaultLocale : la localidad predeterminada de su aplicación (también debe incluirse en locales )❗ Solo se aceptan BCP 47 etiquetas de idioma que siguen el formato del
language- el formatocountry. Para obtener más detalles sobre por qué, consulte el documento de decisiones de diseño.
options (opcional) - Opciones parte de un objeto de configuración Next.js. getConfig devolverá un objeto de configuración Next.js.
Para usarlo, simplemente agregue el siguiente código en next.config.js de su aplicación:
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 No todas las opciones de configuración no son compatibles con getConfig . Si alguna vez usa uno, un mensaje de error lo indicará directamente a la siguiente sección: Configuración avanzada.
Config (configuración avanzada) Si tiene necesidades más avanzadas, puede usar el objeto Config directamente e insertar la configuración requerida por next-multilingual directamente en un next.config.js existente. Los argumentos de Config son casi idénticos a getConfig (menos las options ): consulte su IDE (TSDOC) para obtener más detalles. Aquí hay un ejemplo de cómo se puede usar:
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 ,
}Si necesita personalizar su propia configuración de Webpack, le recomendamos extender nuestro controlador así:
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
} O directamente en 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 hace 2 cosas que aprovechan la capacidad de enrutamiento actual de Next.js:
next-multilingual/config también maneja la configuración especial de Webpack requerida para la representación del lado del servidor de URL localizadas utilizando next-multilingual/link/ssr para los componentes Link y next-multilingual/head/ssr para enlaces canónicos y alternativos en el componente Head .
Para obtener más detalles sobre la implementación, como por qué estamos utilizando caracteres UTF-8, consulte el documento de decisiones de diseño.
next-multilingual/messages/babel-plugin Para mostrar mensajes localizados con el gancho useMessages() , necesitamos configurar nuestro complemento Babel personalizado que inyectará automáticamente cadenas en páginas y componentes. La forma recomendada de hacerlo es incluir un .babelrc en la base de su aplicación:
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
} Si no configura el complemento, recibirá un error al intentar usar useMessages .
App personalizada ( _app.tsx ) Necesitamos crear una App personalizada agregando _app.tsx en el directorio 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 ExampleAppEsto básicamente hace dos cosas, como se menciona en los comentarios:
/ ). Si no desea utilizar la detección local de next-multilingual , puede usar useActualLocale(false) .Document personalizado ( _document.tsx ) También necesitamos crear un Document personalizado agregando _document.tsx en el directorio 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 Esto sirve solo a 1 propósito: Muestre la configuración regional correcta del lado del servidor en la etiqueta <html> . Dado que estamos utilizando un local de predeterminado "falso", es importante mantener el marcado correcto de SSR, especialmente al resolver una ubicación dinámica en / .
next-multilingual/head proporciona un componente <Head> que crea automáticamente un enlace canónico y enlaces alternativos en el encabezado. Esto es algo que Next.js. no proporciona fuera de la caja.js.
NEXT_PUBLIC_ORIGIN Según Google, los enlaces alternativos deben estar completamente calificados, incluido el método de transporte (HTTP/HTTPS). Debido a que Next.js no sabe qué URL se usa en el tiempo de compilación, necesitamos especificar la URL absoluta que se utilizará en una variable de entorno. Por ejemplo, para el entorno de desarrollo, cree un archivo .env.development en la raíz de su aplicación con la siguiente variable (ajuste en función de su configuración):
NEXT_PUBLIC_ORIGIN =http://localhost:3000 Independientemente del entorno, next-multilingual buscará una variable llamada NEXT_PUBLIC_ORIGIN para generar URL totalmente calificadas. Si está utilizando basePath de Next.js, se agregará automáticamente a la URL base.
NEXT_PUBLIC_ORIGIN solo aceptará dominios totalmente calificados (por ejemplo, http://example.com ), sin ninguna ruta.
next-multilingual ? Ahora que todo ha sido configurado, ¡podemos centrarnos en usar next-multilingual !
Para que next-multilingual funcione según lo diseñado, tuvimos que encontrar soluciones a 2 problemas:
undefined : debido a que los sitios de soporte de Next.js sin locales, sus tipos nativos permiten tener valores undefined , lo que para nuestro caso es más una molestia y requiere un casting adicional.next-multilingual tiene que crear una configuración regional predeterminada que nunca usamos. Esto significa que para acceder a la información local relevante, no podemos confiar en las API de Next.js.Creamos las siguientes API para permitir valores de locales consistentes en su aplicación:
useRouter Este es un envoltorio simple encima del useRouter de Next.js que proporciona los lugares correctos, pero que nunca devuelve 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 : { } }
}
️ Tenga en cuenta que si bien recomendamos usar la detección de locales inteligentes para representar dinámicamente la página de inicio, esto es completamente opcional. Al usar la configuración avanzada conlocaleDetection: true, restaurará el comportamiento de Next.js predeterminado sin la necesidad de usargetServerSideProps.
La página de inicio es un poco más compleja que otras páginas, ya que necesitamos implementar la detección de locales dinámicas (y mostrar) por la siguiente razón:
/ puede tener un impacto negativo en el SEO y no es la mejor experiencia de usuario.next-multilingual viene con una API getPreferredLocale que ofrece una detección automática más inteligente que la implementación predeterminada de Next.js.Puede encontrar una implementación completa en el ejemplo, pero aquí hay una versión 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 ) ,
} ,
}
}En pocas palabras, esto es lo que está sucediendo:
next-multilingual .useResolvedLocale para hacer esta dinámica en toda la aplicación. Cada vez que crea un archivo tsx , ts , jsx o js (compilable) y que necesita mensajes localizados, simplemente puede crear un archivo de mensajes en sus locales compatibles que solo se pueden usar con estos archivos. Al igual que los módulos CSS, la idea es que puede tener archivos de mensajes asociados con el alcance local de otro archivo. Esto tiene el beneficio de hacer que los mensajes sean más modulares y también evita compartir mensajes en diferentes contextos (más detalles en el documento de decisiones de diseño sobre por qué esto es malo).
Los archivos de mensajes tienen 2 casos de uso principales:
pages , puede especificar un segmento de URL localizado (parte de una URL intermedia / o al final de la ruta) utilizando el identificador de la tecla slug . Más detalles sobre cómo hacer esto a continuación.useMessages . Imagine CSS pero para cuerdas localizables.Para resumir:
Crear y administrar esos archivos es tan simple como crear una hoja de estilo, pero aquí están los detalles importantes:
.properties . Sí, es posible que se pregunte por qué, pero hay buenas razones documentadas en el documento de decisión de diseño.UTF-8 . No hacerlo reemplazará caracteres no latinos con �.properties , seguimos una estricta convención de nombres: <PageFilename>.<locale>.properties<applicationId>.<context>.<id> DONDE:next-multilingual/configaboutUsPage o footerComponent podría ser buenos ejemplos de contexto. Cada archivo solo puede contener 1 contexto y contexto no debe usarse en muchos archivos, ya que esto podría causar "colisión clave" (claves no únicas).. y solo puede contener entre 1 y 50 caracteres alfanuméricos; recomendamos usar Camel Case for Reality.slug .title .getTitle proporcionada en next-multilingual/messages para retrasar automáticamente el title y las claves slug .useMessages .Además, asegúrese de verificar el registro de su consola para obtener advertencias sobre posibles problemas con sus mensajes. Puede ser difícil acostumbrarse a cómo funciona primero, pero tratamos de facilitar la detección y solucionar problemas. Tenga en cuenta que esos registros solo se mostrarán en entornos de no producción.
No es raro necesitar mensajes localizados mientras no puede usar ganchos. Un ejemplo sería mientras usa una de las características principales de Next.js es su soporte de API integrado. En ese contexto, en lugar de usar useMessage , simplemente podemos usar getMessages al especificar el argumento locale .
Como se mencionó anteriormente, hay una clave especial para pages , donde la id es slug . A diferencia de las babosas tradicionales que se parecen this-is-a-page , le pedimos que escriba la babosa como una oración normal y legible humana, para que pueda traducirse como cualquier otra cadena. Esto evita tener procesos especiales para babosas que pueden ser costosos y complejos de administrar en múltiples idiomas.
Básicamente, la slug es la "descripción breve" legible humana de su página, y representa un segmento (parte entre / o al final de la ruta) de una URL. Cuando se usa como segmento de URL, se aplica la siguiente transformación:
- Por ejemplo, About Us se convertirá en about-us .
Para la página de inicio, la URL siempre será / lo que significa que slug teclas no se utilizarán para crear segmentos de URL localizados.
No olvide, las babosas deben escribirse como una descripción corta normal, lo que significa que se desaconseja omitir palabras para mantenerlo más corto para SEO. La razón principal de esto es que si escribe "un montón de palabras clave", un lingüista que no está familiarizado con SEO podría tener dificultades para traducir ese mensaje. Tener especialistas en SEO en muchos idiomas también sería muy costoso y difícil de escalar. En un escenario ideal, las páginas de SEO específicas del mercado probablemente deberían autorizarse y optimizar en los idiomas nativos, pero esto ya no es parte del proceso de traducción. El enfoque del next-multilingual es proporcionar una solución fácil y simplificada para localizar las URL en muchos idiomas.
La tecla slug también se utilizará como un retroceso de la clave title cuando se use la API getTitle proporcionada en next-multilingual/messages . Esta API facilita la personalización de los títulos cuando una babosa se siente insuficiente.
️ Tenga en cuenta que cambiar un valorslugsignifica que una URL cambiará. Dado que esos cambios están ocurriendo ennext.config.js, como cualquier cambio de configuración Next.js, el servidor debe reiniciarse para ver los cambios en efecto. Lo mismo se aplica si cambia la estructura de la carpeta ya que la configuración subyacente se basa en esto.
Si desea tener un directorio sin ninguna página, aún puede localizarlo creando un index.<locale>.properties ARCHIVO (DONDE locale SON LOS LOCALES QUE SE APLICA). Si bien esta opción es compatible, no recomendamos usarla, ya que esto hará que las rutas de URL sean más largas, lo que va en contra de las mejores prácticas de SEO.
De manera predeterminada, next-multilingual excluirá algunos archivos como páginas de error personalizadas, o cualquier ruta API en el directorio /api . Siempre puede usar claves slug cuando use mensajes para estos archivos, pero no se utilizarán para crear URL localizadas.
Siempre puede buscar el ejemplo para ver archivos de mensajes en acción, pero aquí hay una muestra que podría usarse en la página de inicio:
# Homepage title
exampleApp.homepage.title = Homepage
# Homepage headline
exampleApp.homepage.headline = Welcome to the homepage Ahora que aprendimos a crear la página de inicio y algunos de los detalles sobre cómo funcionan las cosas, podemos crear fácilmente otras páginas. Creamos muchas páginas en el ejemplo, pero aquí hay una muestra de cómo podría verse: about-us.jsx :
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 Y, por supuesto, tendría este archivo de mensaje 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 viene con su propio componente <Link> que permite la representación del lado del cliente y el lado del servidor de la URL localizada. Su uso es simple, funciona exactamente como Next.js ' <Link> .
Lo único importante que debe recordar es que el atributo href siempre debe contener la URL siguiente.js. Es decir, la estructura del archivo en la carpeta pages debe ser lo que se usa y no las versiones localizadas.
En otras palabras, la estructura del archivo se considera la representación de URL "no localizada", y <Link> se encargará de reemplazar las URL con las versiones localizadas (de los archivos de mensajes), si difieren de la estructura.
La API está disponible en next-multilingual/link y puede usarlo así:
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 uno de estos enlaces se localizará automáticamente cuando la tecla slug se especifique en el archivo de mensajes de esa página. Por ejemplo, en el inglés de EE. UU., La ruta URL "Contáctenos" será /en-us/contact-us mientras que en el francés canadiense será /fr-ca/nous-joindre .
Como los datos para esta asignación no están disponibles de inmediato durante la representación, next-multilingual/link/ssr se encargará de la representación del lado del servidor (SSR). Al utilizar next-multilingual/config de getConfig , la configuración de Webpack se agregará automáticamente. Si está utilizando el método Config avanzado, esto explica por qué se requiere la configuración especial de Webpack en el ejemplo proporcionado anteriormente.
No todas las URL localizadas están utilizando el componente <Link> y esta es también la razón por la cual NEXT.JS tiene el método router.push que puede ser utilizado por muchos otros casos de uso. next-multilingual puede respaldar estos casos de uso con el gancho useLocalizedUrl que devolverá una URL localizada, utilizable por cualquier componente. Aquí hay un ejemplo sobre cómo se puede aprovechar:
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 Si prefiere definir sus URL en línea en lugar de en la parte superior del componente, o si necesita hacer manipulaciones de URL más avanzadas, también puede usar el gancho useGetLocalizedUrl que devuelve una función para obtener URL:
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 TestsTenga cuidado, si desea utilizar el valor de cadena de la URL dentro de los elementos React, tendrá errores porque las URL difieren entre la pre-retención y el navegador. La razón de esto es que en el cliente, en First Render, Next.js no tiene acceso a los datos de reescritura y, por lo tanto, utiliza rutas de URL "semi-localizadas" (EG
/fr-ca/about-us). Dado que este es un comportamiento nativo de Next.js, la forma más sencilla de trabajar alrededor de esto por ahora es agregarsuppressHydrationWarning={true}a su elemento. Para trabajar en torno a esto,useGetLocalizedUrltambién devuelve una propiedadisLoadingque se puede usar para rastrear cuando las URL localizadas están disponibles para usar en el cliente.
Es posible que se encuentre con una situación en la que también necesita obtener una URL localizada, pero usar un gancho no es una opción. Aquí es donde entra getLocalizedUrl en next-multilingual/url . Actúa lo mismo que useLocalizedUrl pero su argumento locale es obligatorio.
Imagine usar la API de Next.js para enviar correos electrónicos transaccionales y querer aprovechar las URL localizadas de next-multilingual sin tener que codificarlas en una configuración. Aquí hay un ejemplo de cómo se puede usar:
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 Crear componentes es lo mismo que las páginas, pero viven fuera del directorio pages . Además, la tecla slug (si se usa) no tendrá ningún impacto en las URL. Tenemos algunos componentes de ejemplo que deberían explicarse por sí mismo, pero aquí hay un ejemplo de Footer.tsx componente:
import { useMessages } from 'next-multilingual/messages'
const Footer : React . FC = ( ) => {
const messages = useMessages ( )
return < footer > { messages . format ( 'footerMessage' ) } </ footer >
}
export default FooterY su archivo de mensajes:
# This is the message in the footer at the bottom of pages
exampleApp.footerComponent.footerMessage = © FooterTambién asegúrese de mirar el ejemplo del componente del conmutador de idiomas que es imprescindible en todas las aplicaciones multilingües.
Hemos tenido claro que compartir mensajes es una mala práctica desde el principio, entonces, ¿de qué estamos hablando aquí? De hecho, compartir mensajes por sí solo no es malo. Lo que puede causar problemas es cuando comparte mensajes en diferentes contextos. Por ejemplo, puede tener la tentación de crear un archivo de mensajes compartidos Button.ts que contiene yesButton , noButton Keys, pero esto estaría mal. En muchos idiomas, palabras simples como "sí" y "no" pueden tener diferentes ortografía dependiendo del contexto, incluso si es un botón.
¿Cuándo es bueno compartir mensajes? Para listas de artículos.
Por ejemplo, para mantener su proceso de localización simple, desea evitar almacenar cadenas localizables en su base de datos (más detalles sobre por qué en el documento de decisión de diseño). En su base de datos, identificaría el contexto utilizando identificadores únicos y almacenaría sus mensajes en archivos de mensajes compartidos, donde los identificadores de su clave coincidirían con los de la base de datos.
Para ilustrar esto, creamos un ejemplo usando frutas. Todo lo que necesitas hacer es crear un gancho que llame useMessages como este:
export { useMessages as useFruitsMessages } from 'next-multilingual/messages'❗ Si necesita acceder a sus mensajes fuera de los ganchos, también debe exportar
getMessages.
Por supuesto, tendrá sus archivos de mensajes en el mismo directorio:
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 = LemonY para usarlo, importe simple este gancho desde cualquier lugar donde necesite estos valores:
import { useFruitsMessages } from '../messages/useFruitsMessages'
const FruitList : React . FC ( ) => {
const fruitsMessages = useFruitsMessages ( )
return (
< >
{ fruitsMessages
. getAll ( )
. map ( ( message ) => message . format ( ) )
. join ( ', ' ) }
</ >
)
}
export default FruitListTambién puede llamar a mensajes individuales como este:
fruitsMessages . format ( 'banana' )La idea de compartir esas listas de elementos es que puede tener una experiencia consistente en diferentes componentes. Imagine un menú desplegable con una lista de frutas en una página, y en otra página una entrada automática. Pero la parte importante para recordar es que la lista siempre debe usarse en el mismo contexto, no para reutilizar algunos de los mensajes en un contexto diferente.
Usar marcadores de posición en mensajes es una funcionalidad crítica, ya que no todos los mensajes contienen texto estático. next-multilingual admite la sintaxis de MessageFormat de la UCI fuera del cuadro, lo que significa que puede usar el siguiente mensaje:
exampleApp.homepage.welcome = Hello {name}!E inyectar los valores usando:
messages . format ( 'welcome' , { name : 'John Doe' } ) format Hay algunas reglas simples a tener en cuenta al usar format :
values al formatear el mensaje, simplemente emitirá el mensaje como texto estático.values al formatear el mensaje, debe incluir los valores de todos los marcadores de posición utilizando la sintaxis {placeholder} en su mensaje. De lo contrario, el mensaje no se mostrará.values que no están en su mensaje, serán ignorados en silencio. Uno de los principales beneficios de la UCI MessageFormat es utilizar las herramientas y estándares de Unicode para permitir que las aplicaciones suenen fluida en la mayoría de los idiomas. Muchos ingenieros podrían creer que al tener 2 mensajes, uno para singular y otro para plural es suficiente para mantenerse fluido en todos los idiomas. De hecho, Unicode documentó las reglas plurales de más de 200 idiomas y algunos idiomas como el árabe pueden tener hasta 6 formas plurales.
Para asegurarse de que su oración permanezca fluida en todos los idiomas, puede usar el siguiente mensaje:
exampleApp.homepage.mfPlural = {count, plural, =0 {No candy left.} one {Got # candy left.} other {Got # candies left.}}Y se elegirá la forma plural correcta, utilizando las categorías plurales correctas definidas por Unicode:
messages . format ( 'mfPlural' , { count } )Hay mucho que aprender sobre este tema. Asegúrese de leer la documentación de Unicode y pruebe la sintaxis usted mismo para familiarizarse más con esta capacidad I18N poco hipited.
En un caso raro en el que necesitaría usar ambos marcadores de posición utilizando la sintaxis {placeholder} y también mostrar los caracteres { y } en un mensaje, deberá reemplazarlos por el { (para { ) y } (para } ) Entidades HTML que son reconocidas por herramientas de traducción como esta:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}} Si tiene un mensaje sin valores (marcadores de posición), no se requiere escapar de { y } con entidades HTML y mostrará entidades como texto estático.
Es una situación muy común que necesitamos tener HTML en línea, dentro de un solo mensaje. Una forma de hacer esto sería:
# Bad example, do not ever do this!
exampleApp.homepage.createAccount1 = Please
exampleApp.homepage.createAccount2 = create your account
exampleApp.homepage.createAccount3 = today for free.Y luego:
< div >
{ messages . format ( 'createAccount1' ) }
< Link href = "/sign-up" > { messages . format ( 'createAccount2' ) } </ Link >
{ messages . format ( 'createAccount3' ) }
</ div >Hay 2 problemas con este enfoque:
Esto es en realidad un antipatrón llamado concatenación y siempre debe evitarse. Esta es la forma correcta de hacer esto, usando formatJsx :
exampleApp.homepage.createAccount = Please <link>create your account</link> today for free.Y luego:
< div > { messages . formatJsx ( 'createAccount' , { link : < Link href = "/sign-up" > </ Link > } ) } </ div > formatJsx formatJsx apoya tanto a los marcadores de posición como a los elementos JSX como values , lo que significa que puede beneficiarse de las características format estándar (por ejemplo, plurales) al inyectar elementos JSX.
Hay algunas reglas simples a tener en cuenta al usar format :
formatJsx .<link> XML, el elemento JSX debe proporcionarse usando link: <Link href="/"></Link> .<i> muchas veces en una oración, necesitará crear etiquetas únicas como <i1> , <i2> , etc. y pasar sus valores en argumentos como elementos JSX.Hello <name>{name}</name> )..properties .<Link href="/contact-us><a id="test"></a></Link> es válido pero <div><span1></span1><span2></span2></div> es inválido. En lugar de eso, debe usar el marcado XML de la misma nivel en el archivo .properties y no como un argumento JSX. > < Al usar formatJsx , aún necesitará escapar de los soportes rizados si desea mostrarlos como texto. Además, dado > utilizaremos XML en los mensajes formatJsx , se aplicarán reglas similares a < que se utilizan para identificar etiquetas.
En un caso raro en el que necesitaría inyectar JSX en un mensaje utilizando la sintaxis <element></element> (xml) y > mostrar los < caracteres en un mensaje, deberá reemplazarlos por el < (para < ) y > (para > ) entidades HTML que son reconocidas por herramientas de traducción como esta:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.Los enlaces de anclaje son enlaces que lo llevan a un lugar particular en un documento en lugar de la parte superior.
Una de las características principales de next-multilingual es admitir URL localizadas. Nuestro diseño se ha construido utilizando oraciones normales que son fáciles de localizar y luego se transforman en babosas amigables con el SEO. Podemos usar la misma función para colocar enlaces de anclaje, de modo que en lugar de tener /fr-ca/nous-joindre#our-team puede tener /fr-ca/nous-joindre#notre-équipe .
Hay dos tipos de enlaces de anclaje:
Si los enlaces de anclaje están en la misma página y no se refieren en ninguna otra página, simplemente puede agregarlos en el archivo .properties asociado con esa 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... Y luego la página puede usar la función slugify para vincular al identificador único asociado con el elemento que desea apuntar el fragmento de URL a:
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 También es común usar enlaces de anclaje en páginas, de modo que cuando haga clic en un enlace, su navegador mostrará directamente el contenido relevante en esa página. Para hacer esto, debe poner a disposición del mensaje de su página a otras páginas agregando esta simple exportación que actuará como "mensajes compartidos":
export const useLongPageMessages = useMessagesY luego puede usar este gancho desde otra 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 AnchorLinksEste patrón también funciona para componentes. El beneficio de hacer esto es que si elimina o refactora la página, los enlaces de anclaje asociados con ella siempre permanecerán con la página.
Puede crear un componente de mensaje compartido separado solo para los enlaces de anclaje, pero esto rompería el principio de proximidad.
Se puede encontrar un ejemplo completo de enlaces de anclaje en la aplicación Ejemplo.
Una característica que falta en Next.js es administrar etiquetas HTML importantes utilizadas para SEO. Agregamos el componente <Head> para tratar con dos etiquetas muy importantes que viven en el HTML <head> :
<link rel=canonical> ): Esto le dice a los motores de búsqueda que la fuente de la verdad para la página que se está navegando es esta URL. Es muy importante evitar ser penalizado por contenido duplicado, especialmente porque las URL son insensibles a los casos, pero Google los trata como sensibles a los casos.<link rel=alternate> ): Esto le dice a los motores de búsqueda que la página que se está navegando también está disponible en otros idiomas y facilita el rastreo del sitio. La API está disponible en next-multilingual/head y puede importarlo así:
import Head from 'next-multilingual/head' Al igual que <Link> , <Head> está destinado a ser un reemplazo de entrega para el componente <Head> de Next.js. En nuestro ejemplo, lo estamos usando en el componente de diseño, como este:
< Head >
< title > { title } </ title >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" > </ meta >
</ Head > Todo lo que hace es insertar los enlaces canónicos y alternativos para que los motores de búsqueda puedan rastrear mejor su aplicación. Por ejemplo, si está en la página /en-us/about-us , el siguiente HTML se agregará automáticamente en su etiqueta 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 beneficiarse completamente del marcado de SEO, se debe incluir <Head> en todas las páginas. Hay múltiples formas de lograr esto, pero en el ejemplo, creamos un componente <Layout> que se usa en todas las páginas.
Como la mayoría de los sitios, querrá aprovechar la capacidad de páginas de error personalizadas de Next.js. Con useMessages() , es tan fácil como crear otras páginas. Por ejemplo, para un error 404 , puede crear su 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 Y, por supuesto, sus mensajes, por ejemplo 404.en-US.properties :
# Page title
exampleApp.pageNotFoundError.title = 404 - Page Not Found
# Go back link text
exampleApp.pageNotFoundError.goBack = Go back homeLas API a menudo necesitan ser localizadas. Aquí hay un ejemplo de "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' ) } )
} Esto es muy similar a la API implementada en la aplicación de ejemplo. Estamos utilizando el encabezado HTTP Accept-Language para decirle a la API en la que queremos que sea su respuesta. A diferencia del gancho useMessages que tiene el contexto de la localidad actual, debemos decirle getMessages en qué lugar devuelve los mensajes.
Los archivos de mensajes se comportan exactamente lo mismo que con useMessages , simplemente necesita crear uno junto al archivo de la ruta de la API, en nuestro caso hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.Puede implementar esto en cualquier página, como cualquier otra llamada API basada en 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 >
)
} El normalizeLocale no es obligatorio, pero una convención ISO 3166 recomendada. Dado que Next.js usa los locales como prefijos de URL, son de menor calidad en la configuración y se pueden volver a clasificar según sea necesario.
❗ Las rutas dinámicas son complejas y localizarlas agrega aún más complejidad. Asegúrese de estar familiarizado con cómo funciona esta función Next.js antes de intentar agregar localización.
Las rutas dinámicas son muy comunes y admiten fuera de la caja por Next.js. Desde la versión 3.0, next-multilingual proporciona los mismos soportes que Next.js en términos de rutas dinámicas. Para hacer que las rutas dinámicas funcionen con next-multilingual tenemos algunos patrones a seguir:
href de <Link> y el useLocalizedUrl / useGetLocalizedUrl / getLocalizedUrl url argumento solo aceptan URL de cadena.<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.