Из -за сдвигов в фокусе и приоритетах мы больше не можем поддерживать этот пакет. Он не будет получать обновления, исправления ошибок или новые функции и может стать несовместимым с течением времени. Мы рекомендуем переключиться на пакеты, которые поддерживают последний каталог приложений Next.js.
next-multilingual -это самоуверенное решение для приложений Next.js ✱ , которое требует нескольких языков.
Проверьте наше демонстрационное приложение!
✱ next-multilingual работает только с? Справочник pages . Мы все еще сглаживаем наше решение, чтобы поддержать новое? Справочник app , так как интернационализация больше не является частью конфигурации Next.js.
npm install next-multilingual
useMessages , который поддерживает ICU MessageFormat и JSX -инъекцию из коробки./en-us/contact-us для нас английского языка и /fr-ca/nous-joindre для канадского французского языка) ✱ . ✱ Ваши локальные сливки по умолчанию должны соответствовать файловой системе каталога pages (например, слизняк для «о нас» должен находиться в каталоге about-us ). Если локаль по умолчанию, который вам требуется, использует символы, помимо тех, которые поддерживаются файловой системой, она не была протестирована и, вероятно, не будет работать. Запросы на вытягивание приветствуются?
next-multilingual приложил много усилий, чтобы добавить TSDOC ко всем своим API. Пожалуйста, проверьте непосредственно в своем IDE, если вы не уверены, как использовать определенные API, представленные в наших примерах.
Кроме того, наличие мнения о «лучших практиках» - нелегкая задача. Вот почему мы задокументировали наши дизайнерские решения в специальном документе, с которым можно проконсультироваться здесь. Если вы чувствуете, что некоторые из наших API не предлагают то, что вы ожидаете, обязательно проконсультируйтесь с этим документом, прежде чем открыть проблему.
Для тех, кто предпочитает прыгать прямо в действие, посмотрите в каталоге example для сквозной реализации next-multilingual . В остальном в разделе ниже будет предоставлено полное, шаг за шагом руководство по конфигурации.
Есть много вариантов настройки в next.js для достижения наших целей. next-multilingual в основном заботится о:
Мы предлагаем два API, чтобы упростить этот шаг:
getConfig (Simple Config) Эта функция будет генерировать конфигурацию Next.js, которая будет соответствовать большинству вариантов использования. getConfig принимает следующие аргументы:
applicationId - уникальный идентификатор приложения, который будет использоваться в качестве префикса ключа сообщений.locales - месты вашего приложения.defaultLocale - локаль вашего приложения по умолчанию (оно также должно быть включено в locales )❗ Только языковые теги BCP 47 после
language- форматcountryприняты. Для получения более подробной информации о том, почему, обратитесь к документу Design Recisions.
options (необязательно) - Параметры часть объекта конфигурации Next.js. getConfig вернет объект конфигурации Next.js.
Чтобы использовать его, просто добавьте следующий код в next.config.js : 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 . Если вы когда -нибудь используете один, сообщение об ошибке укажет вам непосредственно на следующий раздел: Advanced Config.
Config (Advance Config) Если у вас есть более расширенные потребности, вы можете напрямую использовать объект Config и вставить конфигурацию, необходимую next-multilingual непосредственно в существующую next.config.js . Аргументы 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 делает 2 вещи, используя следующую возможность текущей маршрутизации:
next-multilingual/config также обрабатывает специальную конфигурацию WebPack, необходимую для рендеринга на стороне сервера локализованных URL-адресов с использованием next-multilingual/link/ssr для компонентов Link и next-multilingual/head/ssr для канонических и альтернативных ссылок в компоненте Head .
Для получения более подробной информации о реализации, например, почему мы используем символы UTF-8, обратитесь к документу Design Recisions.
next-multilingual/messages/babel-plugin Чтобы отобразить локализованные сообщения с помощью useMessages() , нам необходимо настроить наш собственный плагин Babel, который автоматически вводит строки в страницы и компоненты. Рекомендуемый способ сделать это - включить .babelrc в основание вашего приложения:
{
"presets" : [ " next/babel " ],
"plugins" : [ " next-multilingual/messages/babel-plugin " ]
} Если вы не настроите плагин, вы получите ошибку при попытке использовать useMessages .
App ( _app.tsx ) Нам нужно создать пользовательское App , добавив _app.tsx в каталоге 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 ExampleAppЭто в основном делает две вещи, как упоминалось в комментариях:
/ ). Если вы не хотите использовать обнаружение локали next-multilingual , вы можете вместо этого использовать useActualLocale(false) .Document ( _document.tsx ) Нам также необходимо создать пользовательский Document , добавив _document.tsx в каталоге 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 Это служит только 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-адресов. Если вы используете basePath Next.js, он будет добавлен автоматически в базовый URL.
NEXT_PUBLIC_ORIGIN будет принимать только полностью квалифицированные домены (например, http://example.com ) без каких -либо путей.
next-multilingual ? Теперь, когда все было настроено, мы можем сосредоточиться на использовании next-multilingual !
Чтобы привлечь к нему next-multilingual работать так, как спроектировано, нам пришлось найти решения 2 задач:
undefined значения: Поскольку сайты поддержки Next.js без локалов, его нативные типы позволяют иметь undefined значения, которые для нашего случая являются более раздражительностью и требуют дополнительного кастинга.next-multilingual должен создавать локаль по умолчанию, которую мы никогда не используем. Это означает, что для доступа к соответствующей информации о локале мы не можем полагаться на API Next.js.Мы создали следующие API, чтобы разрешить согласованные значения локалов в вашем приложении:
useRouter Это простая обертка поверх useRouter next.js, которая обеспечивает правильные локалы, но также никогда не возвращает 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 : { } }
}
️ Обратите внимание, что, хотя мы рекомендуем использовать Smart Locale Detection для динамического отображения домашней страницы, это совершенно необязательно. Используя расширенную конфигурацию сlocaleDetection: true, вы восстановите поведение по умолчанию Next.js без необходимости использованияgetServerSideProps.
Домашняя страница немного сложнее, чем на других страницах, потому что нам нужно реализовать динамическое обнаружение локали (и отображение) по следующей причине:
/ может оказать негативное влияние на SEO и не является лучшим пользовательским опытом.next-multilingual поставляется с API getPreferredLocale , который предлагает более умное автоматическое обнаружение, чем реализация по умолчанию 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 (компилябель), и что вам нужны локализованные сообщения, вы можете просто создать файл сообщений в поддерживаемых локалах, который будет использоваться только этими файлами. Как и модули CSS, идея заключается в том, что у вас могут быть файлы сообщений, связанные с локальной областью другого файла. Это имеет преимущество в том, чтобы сделать сообщения более модульными, а также избегает обмена сообщениями в разных контекстах (более подробная информация в документе «Решения о дизайне» о том, почему это плохо).
Файлы сообщений имеют 2 основных вариантов использования:
pages вы можете указать локализованный сегмент URL (часть URL -адреса между / или в конце пути), используя идентификатор клавиши slug . Более подробная информация о том, как это сделать ниже.useMessages . Представьте себе CSS, но для локализации струн.Суммировать:
Создать и управлять этими файлами так же просто, как создать лист стилей, но вот важные детали:
.properties . Да, вы можете задаться вопросом, почему, но есть веские причины, зарегистрированные в документе по разработке.UTF-8 . Не это заменит нелатиновые символы на �.properties , мы следуем строгим соглашению от именования: <PageFilename>.<locale>.properties<applicationId>.<context>.<id>next-multilingual/configaboutUsPage или footerComponent может быть хорошими примерами контекста. Каждый файл может содержать только 1 контекст, и контекст не должен использоваться во многих файлах, так как это может привести к «столкновению ключей» (ключи от некновения).. и может содержать только от 1 до 50 буквенно -цифровых символов - мы рекомендуем использовать корпус верблюда для читаемости.slug .title .getTitle , предоставленную в next-multilingual/messages , чтобы автоматически запасаться между title и клавишами slug .useMessages .Кроме того, обязательно проверьте журнал консоли на наличие предупреждений о потенциальных проблемах с вашими сообщениями. Может быть сложно привыкнуть к тому, как это работает в первую очередь, но мы старались облегчить обнаружение и исправление проблем. Обратите внимание, что эти журналы будут отображаться только в непроизводственных средах.
Нередко нуждаются в локализованных сообщениях, не могут использовать крючки. Примером будет использование одной из основных функций next.js является его поддержка API. В этом контексте вместо использования useMessage мы можем просто использовать getMessages , указав аргумент locale .
Как упоминалось ранее, есть один специальный ключ для pages , где id является slug . В отличие от традиционных слизняков, которые выглядят так, как this-is-a-page , мы просим вас написать слизняк как обычное и человеческое читаемое предложение, чтобы его можно было перевести как любая другая строка. Это избегает наличия специальных процессов для слизняков, которые могут быть дорогостоящими и сложными для управления на нескольких языках.
По сути, slug - это человеческое читаемое «короткое описание» вашей страницы и представляет собой сегмент (часть между / или в конце пути) URL. При использовании в качестве сегмента URL применяется следующее преобразование:
- Например, About Us станут about-us .
Для домашней страницы URL всегда будет / что означает, что клавиши slug не будут использоваться для создания локализованных сегментов URL.
Не забывайте, слизняки должны быть написаны как обычное короткое описание, что означает, что пропуск слов, чтобы они были короче для SEO, обескуражены. Основная причина этого заключается в том, что если вы напишете «кучу ключевых слов», лингвист, который не знаком с SEO, может трудно перевести это сообщение. Наличие специалистов по SEO во многих языках также было бы очень дорого и трудно масштабироваться. В идеальном сценарии, специфичные для рынка страницы SEO, вероятно, должны быть авторизованы и оптимизированы на родных языках, но это больше не является частью процесса перевода. next-multilingual сосредоточена на том, чтобы обеспечить простое, упорядоченное решение для локализации URL-адресов на многих языках.
Ключ slug также будет использоваться в качестве отрыва от title ключа при использовании API getTitle , предоставленного в next-multilingual/messages . Этот API позволяет легко настраивать названия, когда слизняк чувствует себя недостаточным.
️ Обратите внимание, что изменение значенияslugозначает, что URL -адрес изменится. Поскольку эти изменения происходят вnext.config.js, как и любое изменение конфигурации Next.js, сервер должен быть перезапущен, чтобы увидеть действующие изменения. То же самое относится и к изменению структуры папок, поскольку базовая конфигурация зависит от этого.
Если вы хотите иметь каталог без каких -либо страниц, вы все равно можете локализовать его locale создав index.<locale>.properties Хотя этот вариант поддерживается, мы не рекомендуем использовать его, так как это сделает 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 следующего.js. Значение, структура файла в папке pages должна быть тем, что используется, а не локализованные версии.
Другими словами, структура файла считается «не локализованным» представлением URL-адреса, и <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). Используя getConfig в next-multilingual/config , конфигурация WebPack будет добавлена автоматически. Если вы используете метод расширенного Config , это объясняет, почему специальная конфигурация WebPack требуется в примере, приведенном ранее.
Не все локализованные URL -адреса используют компонент <Link> , и именно поэтому Next.js имеет метод router.push , который может использоваться многими другими вариантами использования. next-multilingual может поддержать эти варианты использования с помощью крючка useLocalizedUrl , который вернет локализованный 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 , который возвращает функцию для получения 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 TestsБудьте осторожны, если вы хотите использовать строковое значение URL-адреса внутри элементов React, у вас будут ошибки, потому что URL-адреса различаются между предварительным Рендеррингом и браузером. Причина этого заключается в том, что на клиенте, на первом рендеринге, Next.js не имеет доступа к данным переписывает и, следовательно, использует «полу локализованные» пути URL (например
/fr-ca/about-us). Поскольку это нативное поведение Next.js, самый простой способ обойти это на данный момент - добавитьsuppressHydrationWarning={true}к вашему элементу. Чтобы обойти это,useGetLocalizedUrlтакже возвращает свойствоisLoading, которое можно использовать для отслеживания, когда локализованные URL -адреса доступны для использования на клиенте.
Вы можете столкнуться с ситуацией, когда вам также нужно получить локализованный URL, но использование крючка не является вариантом. Именно здесь появляется getLocalizedUrl в next-multilingual/url . Он действует так же, как useLocalizedUrl , но его аргумент locale является обязательным.
Представьте себе, что используете API Next.js для отправки транзакционных электронных писем и желающих использовать локализованные URL-адреса next-multilingual без необходимости жесткого кодирования их в конфигурации. Вот пример того, как его можно использовать:
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 .
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 поддерживает синтаксис MessageFormat Outsformat из ICU, что означает, что вы можете использовать следующее сообщение:
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} , а также отобразить символы { и } в сообщении, вам нужно будет заменить их на { (для { ) и } (Для } ) HTML объекты, которые распознаются такими инструментами перевода, как это:
exampleApp.debuggingPage.variableInfo = Your variable contains the following values: & # x7b;{values}} Если у вас есть сообщение без значений (заполнители), уход { и } с объектами HTML не требуется и будет отображать сущности в качестве статического текста.
Это очень распространенная ситуация, в которой мы должны иметь встроенный 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 >Есть 2 проблемы с этим подходом:
Это на самом деле анти-паттерн, называемый конкатенацией , и его всегда следует избегать. Это правильный способ сделать это, используя 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 , что означает, что вы можете извлечь выгоду из стандартных функций format (например, множества) при введении элементов JSX.
Есть несколько простых правил, которые следует иметь в виду при использовании format :
formatJsx .<link> XML элемент JSX должен быть предоставлен с использованием 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> недействителен. Вместо этого вы должны использовать разметку XML .properties < и > При использовании formatJsx вам все равно нужно будет избежать кудрявых кронштейнов, если вы хотите отобразить их в виде текста. Кроме того, поскольку мы будем использовать XML в сообщениях formatJsx , аналогичные правила будут применяться к < и > , которые используются для идентификации тегов.
В редком случае, где вам нужно будет вводить JSX в сообщение, используя синтаксис <element></element> (XML), а также отобразить символы < и > в сообщении, вам нужно будет заменить их на < (для < ) и > (Для > ) HTML -сущности, которые распознаются такими инструментами перевода, как это:
exampleApp.statsPage.targetAchieved = You achieved your weekly target (& # x3c;5) and are eligible for a <link>reward</link>.Якорные ссылки - это ссылки, которые доставляют вас в определенное место в документе, а не на вершине.
Основной из основной функции next-multilingual является поддержка локализованных URL-адресов. Наш дизайн был построен с использованием обычных предложений, которые легко локализовать, а затем превращены в SEO-дружественные слизняки. Мы можем использовать ту же функцию для ликвидации якорных ссылок, чтобы вместо того, чтобы иметь /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.js, - это управление важными HTML -тегами, используемыми для SEO. Мы добавили компонент <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> , который используется на всех страницах.
Как и большинство сайтов, вы захотите использовать возможности настраиваемых страниц ошибок. С 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, реализованный в примере приложения. Мы используем заголовок HTTP Accept-Language чтобы рассказать API, в котором мы хотим, чтобы его ответ был. В отличие от крючка useMessages , который имеет контекст текущей локали, мы должны сообщить getMessages , в котором локали возвращают сообщения.
Файлы сообщений ведут себя точно так же, как и с useMessages , вам просто нужно создать один рядом с файлом маршрута API, в нашем случае hello.en-US.properties :
# API message
exampleApp.helloApi.message = Hello, from API.Вы можете реализовать это на любых страницах, как и любой другой вызов API на основе React, например:
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 использует локалы в качестве префиксов URL, они находятся в более низком уровне в конфигурации и могут быть повторно нормализованы по мере необходимости.
❗ Динамические маршруты являются сложными и локализуют их, добавляет еще большую сложность. Убедитесь, что вы знакомы с тем, как работает эта функция Next.js, прежде чем пытаться добавить локализацию.
Динамические маршруты очень распространены и поддерживаются из коробки Next.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.