由于重点和优先级的转移,我们无法再维护此软件包。它将不会收到更新,错误修复或新功能,并且随着时间的推移可能变得不兼容。我们建议切换到支持最新next.js应用程序目录的软件包。
next-multilingual是Next.js应用程序的一种自以为是的端到端解决方案✱需要多种语言。
查看我们的演示应用程序!
sext next-multilingual仅与之搭配? pages目录。我们仍在熨烫我们的解决方案以支持新的? app目录由于国际化不再是Next.js的配置的一部分。
npm install next-multilingual
useMessages挂钩,支持ICU MessageFormat和JSX注入开箱即用。/en-us/contact-us for Us English和/fr-ca/nous-joindre for Canadian French) 。 ✱您的默认语言环境sl必须与pages目录文件系统匹配(例如,“关于我们”的slug应该在about-us目录中)。如果您需要的默认场所使用文件系统支持的字符,则该字符尚未进行测试,并且可能无法正常工作。欢迎拉动请求吗?
next-multilingual在将TSDOC添加到所有API中付出了很多努力。如果您不确定如何使用我们示例中提供的某些API,请直接在IDE中检查。
另外,对“最佳实践”有意见并不是一件容易的事。这就是为什么我们在可以在此处咨询的特殊文档中记录了设计决策的原因。如果您觉得我们的某些API不提供您期望的内容,请确保在打开问题之前咨询此文档。
对于那些希望直接跳入该动作的人,请在example目录中查看next-multilingual实现的端到端实现。其余部分,下面的部分将提供完整的,逐步的配置指南。
Next.js中有很多选择可以实现我们的目标。 next-multilingual主要关心:
我们提供两个API来简化此步骤:
getConfig (简单配置)此函数将生成sext.js配置,该配置将符合大多数用例。 getConfig采用以下参数:
applicationId将用作消息密钥前缀的唯一应用程序标识符。locales - 您应用程序的环境。defaultLocale应用程序的默认场所(也必须包含在locales中)❗仅接受遵循
language的语言标签 -country格式。有关原因的更多详细信息,请参阅“设计决策文件”。
options (可选) - next.js配置对象的选项。 getConfig将返回next.js配置对象。
要使用它,只需在应用程序的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 getConfig并不支持所有配置选项。如果您碰巧使用一个,错误消息将直接指向下一节:高级配置。
Config (高级配置)如果您有更多的高级需求,则可以直接使用Config对象,并直接在现有的next.config.js中插入next-multilingual所需的配置。 Config的参数几乎与getConfig (减去options ) - 在您的IDE(TSDOC)中查看有关详细信息。这是如何使用它的示例:
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 ,
}如果您需要自定义自己的webpack配置,我们建议这样做这样的处理程序:
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
}或直接在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在Next.js的当前路由功能上执行2件事:
next-multilingual/config还处理使用next-multilingual/link/ssr用于Link组件的局部URL所需的特殊WebPack配置,用于链接组件中的链接组件和next-multilingual/head/ssr ,用于Head组件中的规范和替代链接。
有关实现的更多详细信息,例如为什么我们使用UTF-8字符,请参阅设计决策文档。
next-multilingual/messages/babel-plugin要显示使用useMessages()挂钩的本地化消息,我们需要配置自定义的Babel插件,该插件将自动将字符串注入页面和组件。建议这样做的方法是在您的应用程序底部包括一个.babelrc :
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
}如果您不配置插件,则在尝试使用useMessages时会遇到错误。
App ( _app.tsx )我们需要通过在pages目录中添加_app.tsx来创建一个自定义App :
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 ExampleApp这基本上做了两件事,如评论中提到的那样:
/ )的主页上登录主页时将其重复使用。如果您不想使用next-multilingual环境检测,则可以使用useActualLocale(false) 。Document ( _document.tsx )我们还需要通过在pages目录中添加_document.tsx来创建自定义Document :
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这仅提供1个目的:在<html>标签中显示正确的服务器端语言环境。由于我们使用的是“假”默认场所,因此保持正确的SSR标记很重要,尤其是在解决/在 /上解决动态语言环境时。
next-multilingual/head提供<Head>组件,该组件会在标题中自动创建一个规范的链接和替代链接。这是Next.js开箱即用的东西。
NEXT_PUBLIC_ORIGIN环境变量根据Google,替代链接必须完全合格,包括传输方法(HTTP/HTTPS)。因为Next.js不知道在构建时间使用哪个URL,因此我们需要在环境变量中指定将使用的绝对URL。例如,对于开发环境,使用以下变量创建一个.env.development开发文件(根据您的设置进行调整):
NEXT_PUBLIC_ORIGIN =http://localhost:3000不管环境如何, next-multilingual将寻找一个称为NEXT_PUBLIC_ORIGIN的变量来生成完全合格的URL。如果您使用的是next.js的basePath ,它将自动添加到基本URL中。
NEXT_PUBLIC_ORIGIN仅接受完全合格的域(例如, http://example.com ://example.com),没有任何路径。
next-multilingual ?现在已经配置了所有内容,我们可以专注于使用next-multilingual !
为了使next-multilingual按设计工作,我们必须找到解决两个问题的解决方案:
undefined值:因为Next.js支持没有地区的网站,因此其本地类型允许具有undefined值,而对于我们的情况来说,这更令人讨厌,需要额外的铸造。next-multilingual必须创建一个我们从未使用过的默认场所。这意味着要访问相关的环境信息,我们不能依靠Next.js的API。我们创建了以下API,以允许在您的应用程序中允许一致的环境值:
useRouter这是Next.js's useRouter上的一个简单包装器,两者都提供了正确的位置,但也永远不会返回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 : { } }
}
配x 请注意,尽管我们建议使用智能语言环境检测来动态渲染主页,但这是完全可选的。通过将高级配置与localeDetection: true,您将不需要使用getServerSideProps还原Next.js行为。
主页比其他页面要复杂一些,因为我们需要出于以下原因实现动态场所检测(和显示):
/可能会对SEO产生负面影响,并且不是最佳的用户体验。next-multilingual带有一个getPreferredLocale API,它提供的自动检测功能比Next.js实现更明智。您可以在示例中找到完整的实现,但这是一个被剥离的版本:
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 ) ,
} ,
}
}简而言之,这就是正在发生的事情:
next-multilingual环境饼干中使用以前使用的语言环境。useResolvedLocale在路由器上覆盖该值,以在整个应用程序上进行这种动态。每当您创建一个tsx , ts , jsx或js (可编译)文件并需要本地化消息时,您就可以在受支持的Locales中创建一个消息文件,而这些文件只能由这些文件可用。就像CSS模块一样,您的想法是,您可以拥有与另一个文件的本地范围关联的消息文件。这具有使消息更模块化的好处,并避免在不同上下文中共享消息(设计决策中的更多详细信息有关为什么这是不好的)。
消息文件有2个主要用例:
pages目录中的页面,您可以使用slug键标识符指定本地化的URL段( /末端或路径末端的URL的一部分)。有关如何执行此操作的更多详细信息。useMessages Hook提供。想象CSS,但对于可本质的字符串。总结:
创建和管理这些文件就像创建样式表一样简单,但以下是重要的细节:
.properties文件。是的,您可能会想知道为什么,但是设计决策文件中有充分的理由。UTF-8 。不这样做会用�代替非拉丁字符 �.properties文件的一些内置IDE支持,我们遵循严格的命名约定: <PageFilename>.<locale>.properties<applicationId>.<context>.<id> where:next-multilingual/config中设置的相同值aboutUsPage或footerComponent上下文可能是上下文的好示例。每个文件只能包含1个上下文,并且不应在许多文件上使用上下文,因为这可能会导致“密钥碰撞”(非唯一的密钥)。.并且只能包含1至50个字母数字字符 - 我们建议使用骆驼盒以进行可读性。slug标识符的密钥的消息文件。title标识符中包含一个键。next-multilingual/messages中提供的getTitle API自动在title和slug密钥之间退缩。useMessages挂钩时才需要文件。另外,请确保检查您的控制台日志是否有关您的消息潜在问题的警告。习惯它首先工作可能很棘手,但是我们试图使检测和解决问题易于检测。请注意,这些日志只会显示在非生产环境中。
在无法使用钩子的同时需要本地化消息并不少见。一个例子是使用Next.js的核心功能之一是其内置API支持。在这种情况下,我们可以在指定locale参数时简单地使用getMessages而不是使用useMessage 。
如前所述,有一个pages的特殊键,其中id是slug 。与看起来像this-is-a-page传统sl,我们要求您以正常和人类可读性的句子写sl,以便可以像其他任何字符串一样翻译。这避免了对sl的特殊过程,这些过程可以用多种语言进行昂贵且复杂的管理。
基本上, slug是您页面的人类可读的“简短描述”,代表URL的一个段( /或路径末端之间的部分)。当用作URL段时,将应用以下转换:
-例如, About Us将变成about-us 。
对于主页,URL将始终为/这意味着不会使用slug键来创建局部URL段。
别忘了,必须将slug写为正常的简短描述,这意味着要跳过单词以使SEO的短词不建议使用。这样做的主要原因是,如果您写“一堆关键字”,那么不熟悉SEO的语言学家可能很难翻译该消息。拥有多种语言的SEO专家也将非常昂贵且难以扩展。在理想的情况下,特定于市场的SEO页面可能应在母语中撰写和优化,但这不再是翻译过程的一部分。 next-multilingual的重点是提供一种简单,简化的解决方案,以将URL定位在许多语言中。
当使用next-multilingual/messages提供的getTitle API时, slug键也将用作title密钥的后备。此API使SLUG感觉不足时可以轻松自定义标题。
配x 请注意,更改slug值意味着URL将会更改。由于这些更改正在next.config.js中发生,就像任何next.js配置更改一样,必须重新启动服务器才能查看生效的更改。如果更改文件夹结构,则同样适用,因为基础配置依赖于此。
如果您想拥有一个没有任何页面的目录,则仍然可以通过创建index.<locale>.properties文件(其中locale是您支持的环境)。尽管支持此选项,但我们不建议使用它,因为这将使URL路径更长,这违背了SEO最佳实践。
默认情况下, next-multilingual将排除某些文件,例如自定义错误页面或/api目录下的任何API路由。在使用这些文件的消息时,您总是可以使用slug键,但是它们不会用于创建局部URL。
您可以随时查看以查看“行动中的消息文件”的示例,但这里是可以在首页上使用的示例:
# Homepage title
exampleApp.homepage.title = Homepage
# Homepage headline
exampleApp.homepage.headline = Welcome to the homepage现在,我们学会了如何创建主页以及有关工作方式的一些细节,我们可以轻松创建其他页面。我们在示例中创建了许多页面,但这里是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当然,您将拥有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带有其自己的<Link>组件,可允许客户端和服务器端渲染局部URL。它的用法很简单,它的工作原理与Next.js' <Link>完全一样。
要记住的唯一重要的事情是, href属性应始终包含下一个url。含义, pages文件夹下的文件结构应为使用的文件,而不是本地化版本。
换句话说,文件结构被认为是“非定位” URL表示形式,如果<link>与结构不同, <Link>将用本地化版本(来自消息文件)来替换URL。
API可在next-multilingual/link下使用,您可以这样使用:
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当该页面的消息文件中指定slug键时,这些链接中的每一个都将自动本地化。例如,在美国英语中,“联系我们”的URL路径将为/en-us/contact-us而在加拿大法国人则将是/fr-ca/nous-joindre 。
由于此映射的数据在渲染过程中没有立即可用, next-multilingual/link/ssr将负责服务器端渲染(SSR)。通过使用next-multilingual/config的getConfig ,将自动添加WebPack配置。如果您使用的是高级Config方法,则说明了为什么在先前提供的示例中需要特殊的WebPack配置。
并非所有局部URL都使用<Link>组件,这也是为什么Next.js具有router.push的原因。 next-multilingual可以用useLocalizedUrl Hook支持这些用例,该用例将返回局部URL,可由任何组件使用。这是一个如何利用它的示例:
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如果您更喜欢在组件的顶部定义内联网站,或者需要进行更高级的URL操作,也可以使用返回函数获取URL的useGetLocalizedUrl Hook:
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 Tests请小心,如果要使用React元素中URL的字符串值,则会出现错误,因为预渲染和浏览器之间的URL不同。这样做的原因是,在客户端,第一次渲染上,next.js无法访问重写数据,因此使用“半定位”的URL路径(例如
/fr-ca/about-us)。由于这是本机的next.js行为,因此目前解决此问题的最简单方法是将suppressHydrationWarning={true}添加到您的元素中。为了解决此问题,useGetLocalizedUrl还返回一个可以用于跟踪何时可在客户端上使用的局部URL的isLoading属性。
您可能会遇到您还需要获取本地化URL的情况,但是使用挂钩不是一个选择。这就是next-multilingual/url中getLocalizedUrl所在地。它的作用与useLocalizedUrl相同,但其locale论点是强制性的。
想象一下,请使用Next.js的API发送交易电子邮件,并希望利用next-multilingual的本地化URL,而无需将其用于配置中。这是如何使用它的示例:
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创建组件与页面相同,但它们生活在pages目录之外。另外, slug键(如果使用)不会对URL产生任何影响。我们有一些应该是自我解释的示例组件,但以下是Footer.tsx的示例。TSX组件:
import { useMessages } from 'next-multilingual/messages'
const Footer : React . FC = ( ) => {
const messages = useMessages ( )
return < footer > { messages . format ( 'footerMessage' ) } </ footer >
}
export default Footer及其消息文件:
# This is the message in the footer at the bottom of pages
exampleApp.footerComponent.footerMessage = © Footer另外,请确保查看所有多语言应用程序中必须是必须的语言切换器组件示例。
我们已经很清楚,共享消息是从一开始的不良习惯,那么我们在这里谈论什么?实际上,共享消息本身还不错。可能导致问题的是您在不同上下文中共享消息时。例如,您可能会很想noButton一个Button.ts yesButton在许多语言中,即使是按钮,诸如“是”和“否”之类的简单单词也可以根据上下文具有不同的拼写。
分享消息什么时候好?对于项目列表。
例如,要使您的本地化过程保持简单,您希望避免在数据库中存储可本质的字符串(有关为什么在设计决策文档中的更多详细信息)。在您的数据库中,您将使用唯一标识符识别上下文,然后将消息存储在共享消息文件中,其中密钥的标识符将与数据库中的标识符匹配。
为了说明这一点,我们使用水果创建了一个示例。您需要做的就是创建一个调用这样的useMessages钩子:
export { useMessages as useFruitsMessages } from 'next-multilingual/messages'❗如果您需要在挂钩之外访问您的消息,则还需要导出
getMessages。
当然,您将在同一目录中使用消息文件:
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 = Lemon要使用它,请简单地从您可能需要以下值的任何地方导入此钩子:
import { useFruitsMessages } from '../messages/useFruitsMessages'
const FruitList : React . FC ( ) => {
const fruitsMessages = useFruitsMessages ( )
return (
< >
{ fruitsMessages
. getAll ( )
. map ( ( message ) => message . format ( ) )
. join ( ', ' ) }
</ >
)
}
export default FruitList您也可以调用这样的单个消息:
fruitsMessages . format ( 'banana' )分享这些项目列表的想法是,您可以在不同组件之间具有一致的经验。想象一下,一个页面中的水果列表,在另一页中有一个自动完成输入的下拉列表。但是要记住的重要部分是,该列表必须始终在相同的上下文中使用,而不是在不同上下文中重新使用某些消息。
在消息中使用占位符是关键功能,因为并非所有消息都包含静态文本。 next-multilingual of Box中支持ICU MessageFormat语法,这意味着您可以使用以下消息:
exampleApp.homepage.welcome = Hello {name}!并使用以下方式注入值:
messages . format ( 'welcome' , { name : 'John Doe' } ) format使用format时,有一些简单的规则要牢记:
values参数,则它将简单地将消息作为静态文本输出。values参数,则必须在消息中使用{placeholder}语法的所有占位符的值。否则将不会显示该消息。values ,它们将被默默地忽略。 ICU MessageFormat的主要好处之一是使用Unicode的工具和标准,以使应用程序能够流利的大多数语言。许多工程师可能会认为,通过有2条消息,一条消息是单数,一个用于复数足以保持所有语言的流利。实际上,Unicode记录了200多种语言的复数规则,而阿拉伯语(例如阿拉伯语)的某些语言最多可以具有6种复数形式。
为了确保您的句子能保持流利的所有语言,您可以使用以下消息:
exampleApp.homepage.mfPlural = {count, plural, =0 {No candy left.} one {Got # candy left.} other {Got # candies left.}}并使用Unicode定义的正确复数类别选择正确的复数形式:
messages . format ( 'mfPlural' , { count } )关于这个话题有很多需要学习的东西。确保阅读Unicode文档,并自己尝试该语法,以更加熟悉这种肆意的I18N功能。
在极少数情况下,您需要使用{placeholder}语法并在消息中显示{ and }字符的两个占位符,您将需要由{ (for { )和} (for } )由这样的翻译工具识别的HTML实体:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}}如果您有没有值(占位符)的消息,则不需要使用HTML实体逃脱{ and } ,并且将显示实体作为静态文本。
这是一个非常普遍的情况,我们需要在一条消息中具有内联html。一种做到这一点的方法是:
# Bad example, do not ever do this!
exampleApp.homepage.createAccount1 = Please
exampleApp.homepage.createAccount2 = create your account
exampleApp.homepage.createAccount3 = today for free.进而:
< div >
{ messages . format ( 'createAccount1' ) }
< Link href = "/sign-up" > { messages . format ( 'createAccount2' ) } </ Link >
{ messages . format ( 'createAccount3' ) }
</ div >这种方法有两个问题:
这实际上是一种称为串联的反图案,应始终避免。这是使用formatJsx正确方法:
exampleApp.homepage.createAccount = Please <link>create your account</link> today for free.进而:
< div > { messages . formatJsx ( 'createAccount' , { link : < Link href = "/sign-up" > </ Link > } ) } </ div > formatJsx formatJsx支持占位符和JSX元素作为values ,这意味着您可以在注入JSX元素时从标准format功能(例如复数)中受益。
使用format时,有一些简单的规则要牢记:
formatJsx的参数。<link> XML标签,需要使用link: <Link href="/"></Link> 。<i> ,也需要创建诸如<i1> , <i2>等的唯一标签,并将其值作为JSX元素传递。Hello <name>{name}</name> )。.properties文件中。<Link href="/contact-us><a id="test"></a></Link>是有效的,但是<div><span1></span1><span2></span2></div>是无效的。相反,您必须在.properties文件中使用相同级别的XML标记,而不是JSX参数。 <和>使用formatJsx时,如果要将其显示为文本,您仍然需要逃脱卷曲括号。此外,由于我们将在formatJsx消息中使用XML,因此类似的规则将适用于用于识别标签的<和> 。
在一个罕见的事件中,您需要使用<element></element> (xml)语法将JSX注入消息中,并且还需要在消息中显示<和>字符,您将需要由< (for < )和> (对于> )通过这样的翻译工具识别的HTML实体:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.锚点链接是将您带到文档中的特定位置而不是顶部的链接。
next-multilingual的核心功能之一是支持本地化的URL。我们的设计是使用易于本地化然后转换为SEO友好型slugs的普通句子建造的。我们可以使用相同的函数来划分锚点链接,因此您可以拥有/fr-ca/nous-joindre#our-team您可以拥有/fr-ca/nous-joindre#notre-équipe 。
有两种类型的锚链接:
如果锚点链接在同一页面上,而在任何其他页面上都没有引用,则可以在此类页面中仅将它们添加到.properties文件中:
# 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...然后,页面可以使用slugify函数链接到与要指向URL片段相关的元素关联的唯一标识符:
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 在跨页面上使用锚点链接也很常见,因此当您单击链接时,浏览器将直接在该页面上显示相关内容。为此,您需要通过添加此简单的导出将与“共享消息”相似的简单导出来使页面的消息可用于其他页面:
export const useLongPageMessages = useMessages然后,您可以从其他页面中使用此钩子:
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 AnchorLinks此模式也适用于组件。这样做的好处是,如果删除或重构页面,与之关联的锚链接将始终与页面保持联系。
您可以仅为锚点链接创建一个单独的共享消息组件,但这将破坏接近原则。
示例链接的完整示例可以在示例应用程序中找到。
Next中缺少的一个功能是管理用于SEO的重要HTML标签。我们添加了<Head>组件来处理HTML <head>中的两个非常重要的标签:
<link rel=canonical> ):这告诉搜索引擎,浏览页面的真相来源是此URL。避免对重复内容受到惩罚非常重要,尤其是因为URL不敏感,但是Google将其视为病例敏感。<link rel=alternate> ):这告诉搜索引擎,浏览页面也可以使用其他语言可用,并促进了网站的爬行。 API可在next-multilingual/head下使用,您可以这样导入:
import Head from 'next-multilingual/head'就像<Link>一样, <Head>是next.js' <Head>组件的倒数替换。在我们的示例中,我们在布局组件中使用它:这样:
< Head >
< title > { title } </ title >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" > </ meta >
</ Head >所有这些都是插入规范和替代链接,以便搜索引擎可以更好地爬网。例如,如果您在/en-us/about-us页面上,则下列HTML将在您的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 " />为了完全受益于SEO标记, <Head>必须包含在所有页面上。有多种方法可以实现这一目标,但是在示例中,我们创建了在所有页面上使用的<Layout>组件。
像大多数站点一样,您将需要利用Next.js的自定义错误页面功能。使用useMessages() ,它与创建任何其他页面一样容易。例如,对于404错误,您可以创建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当然,您的消息,例如404.en-US.properties :
# Page title
exampleApp.pageNotFoundError.title = 404 - Page Not Found
# Go back link text
exampleApp.pageNotFoundError.goBack = Go back homeAPI通常需要进行本地化。这是一个“ 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' ) } )
}这与示例应用程序中实现的API非常相似。我们正在使用Accept-Language HTTP标头来告诉API我们希望在哪个语言环境中进行响应。与具有当前语言环境的上下文的useMessages挂钩不同,我们需要告诉getMessages在哪个语言环境中返回消息。
消息文件的行为与使用useMessages完全相同,您只需要在API路由文件旁边创建一个,在我们的情况下, hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.您可以像任何其他基于React的API调用一样,在任何页面中实现此信息,例如:
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 >
)
} normalizeLocale不是强制性的,而是建议的ISO 3166惯例。由于Next.js将erentes用作URL前缀,因此它们在配置中较低,并且可以根据需要重新归一化。
❗动态路线很复杂,并且将它们定位增加了更复杂的性能。在尝试添加本地化之前,请确保您熟悉this.js功能的工作方式。
动态路线非常普遍,并在下一步受到支持。自3.0版以来, next-multilingual就动态路线提供了与Next.js相同的支持。为了使动态路线与next-multilingual一起使用,我们有几种模式可供遵循:
<Link>的href属性和useLocalizedUrl / useGetLocalizedUrl / getLocalizedUrl url参数仅接受字符串URL。<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.