Format.js позволяет вам переводить элементы пользовательского интерфейса, сообщения и даты в вашем приложении React. В этом руководстве показано, как перевести ваше приложение React с помощью Format.JS. Чтобы следовать этой статье, вам потребуются базовые знания React, терминала и редактора кода.
Обеспечение того, чтобы ваше приложение React было доступно на нескольких языках, является одним из наиболее важных способов гарантировать, что люди во всем мире могут получить к нему доступ в современном глобально интегрированном обществе. Локализация помогает в этой ситуации. Format.js, набор инструментов с открытым исходным кодом, предлагает набор инструментов для локализации вашего приложения React.
Format.js позволяет вам переводить элементы пользовательского интерфейса, сообщения и даты в вашем приложении, что упрощает его использование пользователями за пределами вашей страны. В этом руководстве показано, как перевести ваше приложение React с помощью Format.js.
Чтобы следовать этой статье, вам понадобятся базовые знания React, терминал и редактор кода (рекомендую VS Code). Давайте начнем!
Забежать вперед:
- Начало работы с React
- Построение компонентов
ProductCard
иCart
- Создание функций для добавления в наше приложение React
- Настройка Format.js в React
- Добавление
IntlProvider
- Использование компонента
FormattedMessage
- Перевод компонентов
Cart
иProductItem
- Переведи
Cart
- Переведи
ProductItem
- Переключение между языками
- Создать локальные файлы JSON
- Динамически импортировать сообщения локали
Начало работы с React
Сначала мы используем Create React App для создания приложения React:
npx create-react-app translate-app
Затем мы можем перейти к вновь созданному проекту после установки с помощью команды cd translate-app/
. Давайте быстро настроим очень простое приложение для электронной коммерции, в котором у нас есть куча продуктов, отображаемых в карточках. Наше простое приложение также будет иметь компонент корзины, который показывает продукты, которые мы добавили в нашу корзину, и позволяет нам удалять эти продукты.
Создание компонентов ProductCard
и Cart
Теперь создайте новый файл ./src/components/ProductItem.jsx
и введите следующий код:
// ./src/components/ProductItem.jsx const ProductItem = ({ product, addToCart }) => { return ( <li className="product"> <div className="product-image img-cont"> <img src={product.thumbnail} alt="" /> </div> <div className="product-details"> <h3>{product.title}</h3> <p>{product.description}</p> <span className="product-price">${product.price}</span> </div> <div className="product-actions"> <button disabled={product?.isInCart} onClick={() => addToCart(product)} className="cta" > Add to cart </button> </div> </li> ); }; export default ProductItem;
Код выше показывает, что это простой компонент с двумя реквизитами — product
и addToCart
. Эти компоненты предназначены для информации о продукте, отображаемой на компоненте, и функции добавления указанного продукта в корзину.
Теперь, чтобы создать компонент Cart
, создайте новый файл ./src/components/Cart.jsx
и введите следующий код:
// ./src/components/Cart.jsx const Cart = ({ cart, removeItem }) => { return ( <div className="dropdown"> <div className="trigger group"> <button className="cta">Cart</button> <span className="badge"> {cart?.length}</span> </div> <div className="content"> <aside className="cart"> <header className="cart-header"> <h2>Your Cart</h2> <p> You have <span>{cart.length}</span> items in your cart </p> </header> <ul className="items"> {cart.map((item) => { return ( <li tabIndex={0} key={item.id} className="item"> <div className="item-image img-cont"> <img src={item.thumbnail} alt={item.name} /> </div> <div className="item-details"> <h3>{item.title}</h3> <p className="item-price">${item.price}</p> <button onClick={() => removeItem(item)} className="cta"> Remove </button> </div> </li> ); })} </ul> </aside> </div> </div> ); }; export default Cart;
В этом компоненте мы также принимаем два реквизита. Свойство cart
содержит список товаров, которые были добавлены в корзину. Свойство removeItem
будет использоваться для передачи удаленного элемента родительскому компоненту.
Создание функций для добавления в наше приложение React
Далее, в ./src/App.js
, мы импортируем наши компоненты и создадим несколько функций для получения продуктов из dummyjson.com
. Они будут использоваться для наших фиктивных товаров, для добавления товаров в корзину и для удаления товаров из корзины. Сначала введите следующий код в файл ./src/App.js
:
// ./src/App.js import "./App.css"; import { useEffect, useState } from "react"; import ProductItem from "./components/ProductItem"; import Cart from "./components/Cart"; // function to fetch products from dummyjson.com const getProducts = async () => { try { const res = await fetch("https://dummyjson.com/products"); const data = await res.json(); return data; } catch (error) { console.log({ error, }); return []; } }; function App() { // set up state for products and cart const [products, setProducts] = useState([]); const [cart, setCart] = useState([]); // function to add product to cart const handleAddToCart = (product) => { console.log("product", product); setCart((cart) => { return [...cart, product]; }); setProducts((products) => { return products.map((p) => { if (p.id === product.id) { return { ...p, isInCart: true, }; } return p; }); }); }; // function to remove product from cart const handleRemoveFromCart = (product) => { setCart((cart) => { return cart.filter((p) => p.id !== product.id); }); setProducts((products) => { return products.map((p) => { if (p.id === product.id) { return { ...p, isInCart: false, }; } return p; }); }); }; // fetch products on component mount useEffect(() => { getProducts().then((data) => { setProducts(data.products); }); }, []); return ( <div className="app"> <header className="app-header"> <div className="wrapper"> <div className="app-name">Simple store</div> <div> <Cart cart={cart} removeItem={handleRemoveFromCart} /> </div> </div> </header> <main className="app-main"> <div className="wrapper"> <section className="products app-section"> <div className="wrapper"> <header className="section-header products-header"> <div className="wrapper"> <h2 className="caption">Browse our products</h2> <p className="text"> We have a wide range of products to choose from. Browse our products and add them to your cart. </p> </div> </header> <ul className="products-list"> {products.map((product) => ( <ProductItem key={product.id} product={product} addToCart={handleAddToCart} /> ))} </ul> </div> </section> </div> </main> </div> ); } export default App;
Потрясающий! Для целей этой статьи стили, используемые для создания этого примера проекта, будут помещены в файл ./src/App.css
проекта и доступны на GitHub. Вы также можете скопировать стили из Pastebin или использовать собственные стили.
Теперь, если мы запустим наше приложение, выполнив команду npm start
, мы должны увидеть что-то вроде этого:
Хороший! Далее мы установим и настроим Format.js, чтобы начать перевод нашего приложения React.
Настройка Format.js в React
Чтобы начать настройку Format.js в React, используйте следующие команды:
Install react-intl, a Format.js package for React: npm i -S react react-intl
После установки мы можем получить доступ к различным helper
функциям, компонентам и хукам из Format.js, которые мы можем использовать в нашем приложении React.
Добавление IntlProvider
Этот компонент помогает нам добавить функциональность i18n в наше приложение, предоставляя такие конфигурации, как текущая локаль и набор переведенных строк/сообщений, в корень приложения. Это делает эти конфигурации доступными для различных <Formatted />
компонентов, используемых во всем приложении.
В нашем файле ./src/App.js
мы обернем наше приложение компонентом <IntlProvider />
:
// ./src/App.js // ... import { IntlProvider } from "react-intl"; function App() { // ... // set up state for locale and messages const [locale, setLocale] = useState("es"); const [messages, setMessages] = useState({ "app.name": "Tienda sencilla", }) // ... return ( <IntlProvider messages={messages} key={locale} locale={locale}> {/* ... */} </IntlProvider> ); export default App;
На основе приведенного выше кода мы импортируем компонент IntlProvider
из библиотеки react-intl
.
Мы также устанавливаем состояние для переменных locale
и messages
с начальными значениями "es"
и {"app.name": "Tienda sencilla"}
соответственно.
Затем компонент IntlProvider
используется для переноса содержимого компонента App
. Мы также передаем переменные messages
и locale
в качестве реквизита для файла IntlProvider
. Свойство key
устанавливается в состояние locale
, чтобы заставить React повторно отображать компонент при изменении значения locale
.
Компонент IntlProvider
обеспечивает поддержку интернационализации компонента-оболочки, снабжая его переводами для текущей локали. Используя переменные состояния messages
и locale
, содержимое упакованного компонента можно перевести на основе выбранной локали. В этом примере ключ сообщения app.name
преобразуется в "Tienda sencilla"
для испанской локали. Далее мы воспользуемся компонентом <FormattedMesage />
, чтобы увидеть перевод в действии.
Использование компонента FormattedMessage
Во-первых, мы будем использовать компонент FormattedMessage
для преобразования имени приложения, которое появляется в app-header
в ./src/App.js
, используя свойство "app.name"
из нашего messages
, как показано ниже:
// ./src/App.js // ... import { IntlProvider } from "react-intl"; function App() { // ... // set up state for locale and messages const [locale, setLocale] = useState("es"); const [messages, setMessages] = useState({ "app.name": "Tienda sencilla", }) // ... return ( <IntlProvider messages={messages} key={locale} locale={locale}> <div className="app"> <header className="app-header"> <div className="wrapper"> <div className="app-name"> <FormattedMessage id="app.name" defaultMessage={"Simple Store"} /> </div> {/* ... */} </div> </header> {/* ... */} </div> </IntlProvider> ); export default App;
Здесь мы используем компонент FormattedMessage
из библиотеки react-intl
для перевода имени приложения, которое появляется в файле app-header
. Компонент FormattedMessage
принимает два реквизита: ID
и defaultMessage
. Свойство ID
используется для идентификации сообщения перевода в объекте messages
. В данном случае установлено значение "app.name"
.
Подсказка defaultMessage
используется в качестве запасного сообщения на случай, если перевод для указанного ID
не найден. В данном случае установлено значение "Simple Store"
.
При использовании FormattedMessage
имя приложения переводится в соответствии с текущим выбранным языковым стандартом. Когда переменная состояния locale
изменится, компонент IntlProvider
перерендерится и предоставит FormattedMessage
переводы для новой локали.
При этом у нас должно получиться что-то вроде этого:
Точно так же мы можем перевести другой текст в нашем файле .src/App.js
, добавив дополнительные свойства к объекту messages
и используя <FormattedMessage />
для отображения значений следующим образом:
// ./src/App.js // ... import { IntlProvider } from "react-intl"; function App() { // ... // set up state for locale and messages const [locale, setLocale] = useState("es"); const [messages, setMessages] = useState({ "app.name": "Tienda sencilla", "app.description": "Una tienda sencilla con React", "app.products.caption": "Explora nuestros productos", "app.products.text": "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.", }) // ... return ( <IntlProvider messages={messages} key={locale} locale={locale}> <div className="app"> {/* ... */} <main className="app-main"> <div className="wrapper"> <section className="products app-section"> <div className="wrapper"> <header className="section-header products-header"> <div className="wrapper"> <h2 className="caption"> <FormattedMessage id="app.products.caption" defaultMessage={"Browse our products"} /> </h2> <p className="text"> <FormattedMessage id="app.products.text" defaultMessage={"We have a wide range of products to choose from. Browse our products and add them to your cart."} /> </p> </div> </header> <ul className="products-list"> {products.map((product) => ( <ProductItem key={product.id} product={product} addToCart={handleAddToCart} /> ))} </ul> </div> </section> </div> </main> </div> </IntlProvider> ); export default App;
Здесь мы видим, как использовать <FormattedMessage />
для перевода другого текста в файле ./src/App.js
путем добавления дополнительных свойств к объекту messages
. В этом примере messages
содержит свойства для имени приложения, описания, заголовка продукта и текста продукта.
Компонент отображает эти значения, передавая соответствующий ID
в FormattedMessage
вместе со значением по умолчанию message
для отображения, если перевод недоступен. В этом примере компонент отображает заголовок продукта и текст, используя ID
, соответствующий ключам свойств в объекте messages
, с резервным текстом, переданным в defaultMessage
. При этом у нас должно получиться что-то вроде этого:
Хороший!
Перевод компонентов Cart
и ProductItem
Мы можем пойти еще дальше, переведя наши компоненты Cart
и ProductItem
. Во-первых, нам нужно добавить переводы в объект messages
в ./src/App.js
с помощью кода ниже:
const [messages, setMessages] = useState({ "app.name": "Tienda sencilla", "app.description": "Una tienda sencilla con React", "app.products.caption": "Explora nuestros productos", "app.products.text": "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.", "app.cart": "Carrito", "app.cart.title": "Tu carrito", "app.cart.empty": "El carrito está vacío", "app.cart.items": "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito", "app.cart.remove": "Eliminar", "app.cart.add": "Añadir a la cesta", "app.item.price": "{price, number, ::currency/EUR}", });
Здесь мы обновляем объект messages
в ./src/App.js
, добавляя переводы для новых компонентов. Переводы включают такие строки, как "app.cart.title"
, "app.cart.empty"
, "app.cart.items"
и "app.item.price"
. Эти переводы будут отображать правильный текст в компонентах Cart
и ProductItem
.
Перевести Cart
Двигаясь вперед, мы переведем компонент Cart
. Продолжайте и добавьте приведенный ниже код в ./src/components/Cart.jsx
:
// ./src/components/Cart.jsx import { FormattedMessage } from "react-intl"; const Cart = ({ cart, removeItem }) => { return ( <div className="dropdown"> <div className="trigger group"> <button className="cta"> <FormattedMessage id="app.cart" defaultMessage="Cart" /> </button> <span className="badge"> {cart?.length}</span> </div> <div className="content"> <aside className="cart"> <header className="cart-header"> <h2> <FormattedMessage id="app.cart.title" defaultMessage="Your Cart" /> </h2> <p> <FormattedMessage id="app.cart.items" defaultMessage={`You have {count, plural, =0 {no items} one {one item} other {# items}} in your cart`} values={{ count: cart.length }} /> </p> </header> <ul className="items"> {cart.map((item) => { return ( <li tabIndex={0} key={item.id} className="item"> <div className="item-image img-cont"> <img src={item.thumbnail} alt={item.name} /> </div> <div className="item-details"> <h3>{item.title}</h3> <p className="item-price"> <FormattedMessage id="app.item.price" defaultMessage={`{price, number, ::currency/USD}`} values={{ price: item.price }} /> </p> <button onClick={() => removeItem(item)} className="cta"> <FormattedMessage id="app.cart.remove" defaultMessage="Remove" /> </button> </div> </li> ); })} </ul> </aside> </div> </div> ); }; export default Cart;
В приведенном выше коде мы используем FormattedMessage
для отображения переведенных строк. Компонент FormattedMessage
принимает реквизит ID
, соответствующий ключу перевода объекта message
. Он также принимает реквизит defaultMessage
, который отображает значение по умолчанию, если перевод не найден.
Более 200 000 разработчиков используют LogRocket для создания лучшего цифрового опыта
Например, FormattedMessage
с id="app.cart.title"
и defaultMessage="Your Cart"
отображает текст "Tu carrito"
на испанском языке, когда locale
установлено на "es"
.
Внимательно посмотрите на блок кода ниже:
<p> <FormattedMessage id="app.cart.items" defaultMessage={`You have {count, plural, =0 {no items} one {one item} other {# items}} in your cart`} values={{ count: cart.length }} /> </p> Here, "app.cart.items" corresponds to: const [messages, setMessages] = useState({ "app.cart.items": "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito", });
Обратите внимание, что шаблон message
использует count
в качестве переменной, представляющей количество товаров в корзине. Имеет три возможных варианта в зависимости от значения count
:
=0 {no items}:
: если значениеcount
равно нулю, используется этот параметр, и в сообщении будет указано"no items"
.one {one item}:
: если значениеcount
равно единице, используется этот параметр, и в сообщении будет указано"one item"
.other {# items}:
: если значениеcount
отличается от нуля или единицы, используется этот параметр, и в сообщении будет указано# items
, где#
заменяется значениемcount
.
Это для сообщения, указанного в defaultMessage
, и сообщения, указанного в состоянии messages
. Это строка message
, которая включает форму множественного числа на испанском языке. Сообщение содержит переменную count
, используемую для определения правильной множественной формы сообщения. Синтаксис формы множественного числа: {count, plural, ...}
.
В этом синтаксисе первый аргумент — это имя переменной (в данном случае count
), а второй — тип используемого множественного числа. Внутри аргумента множественного числа есть три падежа:
=0 {No tienes artículos}
: Это тот случай, когда переменнаяcount
равна нулю, значит корзина пуста. Сообщение в данном случаеNo tienes artículos
(у вас нет предметов)one {# articulo}
: это когда переменнаяcount
равна единице. Сообщение в данном случае"# articulo"
(один элемент)other {# artículos}
: случай по умолчанию для всех остальных счетчиков. Сообщение в этом случае будет"# artículos"
(X элементов), где X — значение переменнойcount
.
Таким образом, полное сообщение на испанском языке будет выглядеть так: "No tienes artículos"
(у вас нет товаров) для пустой корзины, "1 artículo"
(1 товар) для корзины с одним товаром и "# artículos"
для корзин с двумя или более товарами. Это следует за Intl MessageFormat
, о котором вы можете узнать больше в документации.
Перевести ProductItem
Для компонента ProductItem
в ./src/components/ProductItem.jsx
добавьте следующий код:
// ./src/components/ProductItem.jsx import { FormattedMessage } from "react-intl"; const ProductItem = ({ product, addToCart }) => { return ( <li className="product"> <div className="product-image img-cont"> <img src={product.thumbnail} alt="" /> </div> <div className="product-details"> <h3>{product.title}</h3> <p>{product.description}</p> <span className="product-price"> <FormattedMessage id="app.item.price" defaultMessage={`{price, number, ::currency/USD}`} values={{ price: product.price }} /> </span> </div> <div className="product-actions"> <button disabled={product?.isInCart} onClick={() => addToCart(product)} className="cta" > <FormattedMessage id="app.cart.add" defaultMessage="Add to Cart" /> </button> </div> </li> ); }; export default ProductItem;
Одна вещь, на которую мы должны обратить внимание, это product-price
, как показано ниже:
<span className="product-price"> <FormattedMessage id="app.item.price" defaultMessage={`{price, number, ::currency/USD}`} values={{ price: product.price }} /> </span> "app.item.price" corresponds to: const [messages, setMessages] = useState({ "app.item.price": "{price, number, ::currency/EUR}", });
В приведенном выше коде "{price, number, ::currency/EUR}"
— это шаблон формата сообщения, используемый в библиотеке react-intl
. Он указывает, как форматировать переменную price
как число с символом валюты EUR
.
Фигурные скобки {}
обозначают заполнители в шаблоне сообщения. Между тем, price
— это имя переменной, которую следует подставить в шаблон. Ключевое слово number
указывает, что переменная должна быть отформатирована как число.
Параметр ::currency/EUR
указывает, что число должно быть отформатировано как значение валюты с использованием символа валюты EUR
. После всего этого наше приложение должно быть полностью переведено:
Потрясающий!
Переключение между языками
Последняя и важная функция, которую нужно добавить в наше приложение, — это функция переключения языка. Следуйте ниже.
Создание файлов JSON локали
Сначала мы создадим файлы JSON для каждой локали. Для испанской локали мы создаем новый файл ./src/locales/es.json
, как показано ниже:
{ "app.name": "Tienda sencilla", "app.description": "Una tienda sencilla con React", "app.products.caption": "Explora nuestros productos", "app.products.text": "Tenemos una amplia gama de productos para elegir. Explora nuestros productos y agrégalos a tu carrito.", "app.cart": "Carrito", "app.cart.title": "Tu carrito", "app.cart.empty": "El carrito está vacío", "app.cart.items": "{count, plural, =0 {No tienes artículos} one {# articulo} other {# artículos }} en tu carrito", "app.cart.remove": "Eliminar", "app.cart.add": "Añadir a la cesta", "app.item.price": "{price, number, ::currency/EUR}" } For the English locale, we create a ./src/locales/en.json file: { "app.name": "Simple store", "app.description": "A simple store with React", "app.products.caption": "Explore our products", "app.products.text": "We have a wide range of products to choose from. Explore our products and add them to your cart.", "app.cart": "Cart", "app.cart.title": "Your Cart", "app.cart.empty": "Your cart is empty", "app.cart.items": "{count, plural, =0 {You have no items} one {You have one item} other {You have # items}} in your cart", "app.cart.remove": "Remove", "app.cart.add": "Add to cart", "app.item.price": "{price, number, ::currency/USD}" }
Браво!
Динамически импортировать сообщения локали
Теперь мы будем использовать useEffect
Hook для асинхронной загрузки сообщений для выбранной локали при монтировании компонента или при изменении состояния locale
. Вот код:
// ./src/App.js // ... function App() { // .... const [locale, setLocale] = useState("es"); const [messages, setMessages] = useState({ // ... }); // ... // function to dynamically import messages depending on locale useEffect(() => { import(`./locales/${locale}.json`).then((messages) => { console.log({ messages, }); setMessages(messages); }); }, [locale]); return ( // ... ) }; export default App;
В приведенном выше коде используется динамический импорт для загрузки файла JSON, содержащего сообщения для выбранной локали. Как только сообщения загружены, он устанавливает состояние messages
с загруженными сообщениями. Наконец, добавьте ввод select
для переключения между локалями, как показано ниже:
// ./src/App.js // ... function App() { // ... return ( <IntlProvider messages={messages} key={locale} locale={locale}> <div className="app"> <header className="app-header"> <div className="wrapper"> <div className="app-name"> <FormattedMessage id="app.name" defaultMessage={"Simple Store"} /> </div> <div style={{ display: "flex", gap: "1rem" }}> <Cart cart={cart} removeItem={handleRemoveFromCart} /> <select onChange={(e) => { setLocale(e.target.value); }} value={locale} name="language-select" id="language-select" className="select-input" > <option value="es">Español</option> <option value="en">English</option> </select> </div> </div> </header> {/* ... */} </div> </IntlProvider> ); } export default App;
При этом у нас должно получиться что-то вроде этого:
Потрясающий.
Заключение
В этой статье мы узнали, как использовать Format.js для перевода вашего приложения React. В конечном итоге, следуя инструкциям в этой статье, вы сможете быстро перевести свое React-приложение с помощью Format.js, расширив свою аудиторию и улучшив UX своего приложения.