Лучшая интернационализация для Гэтсби

Вы ведь знаете Gatsby? — Если нет, то прекратите читать эту статью и займитесь чем-нибудь другим.

Да, Gatsby — это фреймворк с открытым исходным кодом, который объединяет функциональность React, GraphQL и Webpack в единый инструмент для создания статических веб-сайтов и приложений.

Но как выглядит интернационализация (i18n) в Gatsby?

Есть несколько плагинов/библиотек, которые могут помочь с инструментарием кода Gatsby для интернационализации.
В этой статье мы будем использовать плагин, основанный на известном i18n-фреймворке i18next, соответственно его замечательному расширению для React.js — react-i18next.

Плагин Gatsby, который мы используем, — gatsby-plugin-react-i18next, созданный Дмитрием Невзоровым.

TOC

  • Итак, прежде всего: «Почему i18next?».
  • Давайте разберемся…
    • Предварительные условия
    • Начало работы
    • Переключатель языков
    • Интернационализированные ссылки
    • Интерполяция и плюрализация
    • Форматирование
    • Контекст
    • Извлечение ключей
    • Обязательно!
    • Как это выглядит?
    • 👀 но это еще не все… (InContext Editor)
  • 🎉🥳 Поздравляем 🎊🎁

Итак, прежде всего: «Почему i18next?».

Когда речь заходит о локализации React. Одним из самых популярных i18n framework является i18next с его react расширением react-i18next, и на то есть веские причины:

i18next был создан в конце 2011 года. Это старше, чем большинство библиотек, которые вы используете сегодня, включая ваши основные фронтенд-технологии (react, vue, …).

➡️ устойчивый

Судя по тому, как долго i18next уже доступен с открытым исходным кодом, нет ни одного реального случая i18n, который нельзя было бы решить с помощью i18next.

➡️ зрелый

i18next может быть использован в любой среде javascript (и нескольких не javascript — .net, elm, iOS, android, ruby, …), с любым фреймворком UI, с любым форматом i18n, … возможности безграничны.

➡️ расширяемость

В i18next вы получите множество функций и возможностей по сравнению с другими обычными i18n-фреймворками.

➡️ богатый

Здесь вы можете найти больше информации о том, почему i18next особенный и как он работает.

Давайте разберемся…

Предварительные условия

Убедитесь, что у вас установлен Node.js и npm. Лучше всего, если у вас есть некоторый опыт работы с простым HTML, JavaScript, React.js и базовым Gatsby, прежде чем переходить к gatsby-plugin-react-i18next. Этот пример локализации Gatsby не является учебником для начинающих по Gatsby или React.

Начало работы

Возьмите свой собственный проект Gatsby или создайте новый, т.е. с помощью gatsby-cli.

npx gatsby-cli new

Мы создадим переключатель языков, чтобы контент менялся между разными языками.

Давайте установим некоторые зависимости i18next:

  • gatsby-plugin-react-i18next
  • i18next
  • react-i18next

npm install gatsby-plugin-react-i18next i18next react-i18next

Создайте директорию locales и добавьте в нее подпапку для вашего языка по умолчанию/справочного языка (например, en для английского).

Туда мы добавим наши файлы пространства имен, например:

|-- en
    |-- common.json
    |-- index.json
Войти в полноэкранный режим Выйти из полноэкранного режима

Добавим файл languages.js:

const { join } = require('path')
const { readdirSync, lstatSync } = require('fs')

const defaultLanguage = 'en';

// based on the directories get the language codes
const languages = readdirSync(join(__dirname, 'locales')).filter((fileName) => {
  const joinedPath = join(join(__dirname, 'locales'), fileName)
  const isDirectory = lstatSync(joinedPath).isDirectory()
  return isDirectory
});
// defaultLanguage as first
languages.splice(languages.indexOf(defaultLanguage), 1);
languages.unshift(defaultLanguage);

module.exports = {
  languages,
  defaultLanguage,
};
Войти в полноэкранный режим Выйти из полноэкранного режима

Импортируйте файл languages.js в файл gatsby-config.js и настройте некоторые плагины:

const { languages, defaultLanguage } = require('./languages');
// somewhere in your plugins add:
module.exports = {
  // ...
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/locales`,
        name: `locale`
      }
    },
    {
      resolve: 'gatsby-plugin-react-i18next',
      options: {
        languages,
        defaultLanguage,
        siteUrl,
        i18nextOptions: {
          // debug: true,
          fallbackLng: defaultLanguage,
          supportedLngs: languages,
          defaultNS: 'common',
          interpolation: {
            escapeValue: false, // not needed for react as it escapes by default
          }
        },
      },
    },
    // ...
  ]
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь приступим к созданию нашего первого интернационализированного текста.

Поскольку gatsby-plugin-react-i18next экспортирует все методы и компоненты react-i18next, мы можем это сделать:

В файле страницы:

import { Trans, useTranslation } from 'gatsby-plugin-react-i18next';
import { graphql } from 'gatsby';
import React from 'react';
// ...

const IndexPage = () => {
  const { t } = useTranslation();
  return (
    <Layout>
      <Seo title={t('seo')} />
      <h1>
        <Trans i18nKey="title">Hi people</Trans>
      </h1>
      { /* ... */}
    </Layout>
  )
}

export default IndexPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(
      filter: { ns: { in: ["index"] }, language: { eq: $language } }
    ) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь также определите файл пространства имен locales/en/index.json, вот так:

{
  "seo": "Home",
  "title": "Hi people"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

И, возможно, еще один для немецкого языка?

locales/de/index.json:

{
  "seo": "Startseite",
  "title": "Hallo Leute"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Переключатель языков

Чтобы иметь возможность переключаться между разными языками, нам нужен переключатель языков:

import { Link, useI18next } from 'gatsby-plugin-react-i18next';
import React from 'react';

const Header = ({ siteTitle }) => {
  const { languages, originalPath, t, i18n } = useI18next();
  return (
    <header className="main-header">
      {/* ... */}
      <ul className="languages">
        {languages.map((lng) => (
          <li key={lng}>
            <Link to={originalPath} language={lng} style={{ textDecoration: i18n.resolvedLanguage === lng ? 'underline' : 'none' }}>
              {lng}
            </Link>
          </li>
        ))}
      </ul>
    </header>
  );
};

export default Header;
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вы должны увидеть что-то вроде этого:

По умолчанию, при первой загрузке, gatsby-plugin-react-i18next будет возвращаться к defaultLanguage, если обнаруженный язык браузера не включен в массив languages.

Если вы хотите сделать откат на другой язык из массива languages, вы можете установить опцию fallbackLanguage.

Теперь переключение на de (немецкий) также должно работать:

🥳 Потрясающе, вы только что создали свой первый переключатель языков!

Интернационализированные ссылки

Давайте создадим вторую страницу…

import { graphql } from 'gatsby';
import React, { useState } from 'react';
import Layout from '../components/layout';
import { useTranslation } from 'gatsby-plugin-react-i18next';

const SecondPage = (props) => {
  const { t } = useTranslation();
  const [count, setCounter] = useState(0);
  return (
    <Layout>
      <Seo title={t('title')} />
      <h1>
        <Trans i18nKey="title">Page two</Trans>
      </h1>
      <p>
        <Trans i18nKey="welcome">Welcome to page 2</Trans> ({props.path})
      </p>
      {/* ... */}
    </Layout>
  );
};

export default SecondPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(
      filter: { ns: { in: ["page-2"] }, language: { eq: $language } }
    ) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;
Вход в полноэкранный режим Выход из полноэкранного режима

Новое пространство имен:
locales/en/page-2.json

{
  "title": "Page two",
  "welcome": "Welcome to page 2"
}
Вход в полноэкранный режим Выход из полноэкранного режима

locales/de/page-2.json

{
  "title": "Seite zwei",
  "welcome": "Willkommen auf Seite 2"
}
Войти в полноэкранный режим Выход из полноэкранного режима

…и ссылку на эту страницу с первой:

import { Link, Trans, useTranslation } from 'gatsby-plugin-react-i18next';
import { graphql } from 'gatsby';
import React from 'react';
// ...

const IndexPage = () => {
  const { t } = useTranslation();
  return (
    <Layout>
      <Seo title={t('seo')} />
      <h1>
        <Trans i18nKey="title">Hi people</Trans>
      </h1>
      { /* ... */}
      <p>
        <Link to="/page-2/">
          <Trans i18nKey="goToPage2">Go to page 2</Trans>
        </Link>
      </p>
    </Layout>
  )
}

export default IndexPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(
      filter: { ns: { in: ["index"] }, language: { eq: $language } }
    ) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;
Войти в полноэкранный режим Выход из полноэкранного режима

Новый ключ перевода для locales/en/index.json:

{
  "seo": "Home",
  "title": "Hi people",
  "goToPage2": "Go to page 2"
}
Войти в полноэкранный режим Выход из полноэкранного режима

locales/de/index.json:

{
  "seo": "Startseite",
  "title": "Hallo Leute",
  "goToPage2": "Gehen Sie zu Seite 2"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Компонент Link, экспортируемый из gatsby-plugin-react-i18next, автоматически устанавливает ссылку на нужный язык.

Компонент Link идентичен компоненту Gatsby Link, за исключением того, что вы можете указать дополнительный реквизит языка для создания ссылки на страницу с другим языком.

Интерполяция и плюрализация

i18next не ограничивается предоставлением стандартных функций i18n.
Но, безусловно, он способен обрабатывать множественное число и интерполяцию.

Давайте посчитаем каждый раз, когда нажимается кнопка:

import { graphql } from 'gatsby';
import React, { useState } from 'react';
import Layout from '../components/layout';
import { useTranslation } from 'gatsby-plugin-react-i18next';

const SecondPage = (props) => {
  const { t } = useTranslation();
  const [count, setCounter] = useState(0);
  return (
    <Layout>
      <Seo title={t('title')} />
      <h1>
        <Trans i18nKey="title">Page two</Trans>
      </h1>
      <p>
        <Trans i18nKey="welcome">Welcome to page 2</Trans> ({props.path})
      </p>
      <p>
        <button onClick={() => {
          setCounter(count + 1);
        }}>{
          t('counter', { count })
        }</button>
      </p>
      {/* ... */}
    </Layout>
  );
};

export default SecondPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(
      filter: { ns: { in: ["page-2"] }, language: { eq: $language } }
    ) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;
Вход в полноэкранный режим Выход из полноэкранного режима

…и расширение ресурсов перевода:
locales/en/page-2.json

{
  "title": "Page two",
  "welcome": "Welcome to page 2",
  "counter_one": "clicked one time",
  "counter_other": "clicked {{count}} time",
  "counter_zero": "Click me!"
}
Вход в полноэкранный режим Выход из полноэкранного режима

locales/de/page-2.json

{
  "title": "Seite zwei",
  "welcome": "Willkommen auf Seite 2",
  "counter_one": "einmal angeklickt",
  "counter_other": "{{count}} Mal geklickt",
  "counter_zero": "Klick mich!"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

На основе значения count i18next выберет правильную форму множественного числа.

i18next также предоставляет возможность специального перевода для {count: 0}, чтобы можно было использовать более естественный язык. Если count равно 0, и присутствует запись _zero, то она будет использоваться вместо суффикса множественного числа обычного языка (_other).

Подробнее о множественном числе и интерполяции читайте в официальной документации i18next.

💡 i18next также может работать с языками с несколькими формами множественного числа, например арабским:

// translation resources:
{
  "key_zero": "zero",
  "key_one": "singular",
  "key_two": "two",
  "key_few": "few",
  "key_many": "many",
  "key_other": "other"
}

// usage:
t('key', {count: 0}); // -> "zero"
t('key', {count: 1}); // -> "singular"
t('key', {count: 2}); // -> "two"
t('key', {count: 3}); // -> "few"
t('key', {count: 4}); // -> "few"
t('key', {count: 5}); // -> "few"
t('key', {count: 11}); // -> "many"
t('key', {count: 99}); // -> "many"
t('key', {count: 100}); // -> "other"
Войти в полноэкранный режим Выход из полноэкранного режима

Почему мои клавиши множественного числа не работают?

Вы видите это предупреждение в консоли разработки (debug: true)?

i18next::pluralResolver: Ваша среда, похоже, не совместима с Intl API, используйте полифилл Intl.PluralRules. Будет откат к обработке формата compatibilityJSON v3.

В v21 i18next упорядочил суффикс с тем, который используется в Intl API.
В средах, где Intl.PluralRules API недоступен (например, на старых устройствах Android), вам может понадобиться полифилл Intl.PluralRules API.
В случае его отсутствия будет выполнен откат к обработке множественного числа в формате i18next JSON v3. И если ваш json уже использует новые суффиксы, ваши ключи множественного числа, вероятно, не будут отображаться.

tldr;

npm install intl-pluralrules

import 'intl-pluralrules'
Вход в полноэкранный режим Выход из полноэкранного режима

Форматирование

Теперь давайте проверим, как с помощью i18next и Luxon можно использовать различные форматы для работы с датой и временем.

npm install luxon

Нам нравится, когда в нижнем колонтитуле отображается текущая дата:

import React from 'react';
import { DateTime } from 'luxon';
import { useI18next } from 'gatsby-plugin-react-i18next';
// ...

const Layout = ({ children }) => {
  const { t, i18n } = useI18next();

  // defining custom formatters is normally done immediately after the i18next.init call, but with gatsby-plugin-react-i18next is not possible, so let's add it here
  if (!i18n.services.formatter.date_huge) {
    i18n.services.formatter.add('date_huge', (value, lng, options) => {
      return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
    });
  }

  return (
    <>
      <Header />
      <div
        style={{
          margin: '0 auto',
          maxWidth: 960,
          padding: '0 1.0875rem 1.45rem',
        }}
      >
        <main>{children}</main>
        <footer style={{ marginTop: 50 }}>
          <i>
            {
              t('footer', { date: new Date() })
            }
          </i>
        </footer>
      </div>
    </>
  );
};

export default Layout;
Вход в полноэкранный режим Выход из полноэкранного режима

Импортируйте luxon и определите функцию формата, как описано в документации, и добавьте новый ключ перевода:

locales/en/common.json

{
  "footer": "Today is {{date, date_huge}}"
}
Войти в полноэкранный режим Выход из полноэкранного режима

locales/de/common.json

{
  "footer": "Heute ist {{date, date_huge}}"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

😎 Круто, теперь у нас есть форматирование даты в зависимости от языка!

Английский:

Немецкий:

Контекст

Как насчет специфического приветствия, основанного на текущем времени суток? Например, утро, вечер и т.д.
Это возможно благодаря контекстной функции i18next.

Давайте создадим функцию getGreetingTime и используем результат в качестве контекстной информации для перевода колонтитула:

import React from 'react';
import { DateTime } from 'luxon';
import { useI18next } from 'gatsby-plugin-react-i18next';
// ...

const getGreetingTime = (d = DateTime.now()) => {
  const split_afternoon = 12; // 24hr time to split the afternoon
  const split_evening = 17; // 24hr time to split the evening
  const currentHour = parseFloat(d.toFormat('hh'));

  if (currentHour >= split_afternoon && currentHour <= split_evening) {
    return 'afternoon';
  } else if (currentHour >= split_evening) {
    return 'evening';
  }
  return 'morning';
}

const Layout = ({ children }) => {
  const { t, i18n } = useI18next();

  // defining custom formatters is normally done immediately after the i18next.init call, but with gatsby-plugin-react-i18next is not possible, so let's add it here
  if (!i18n.services.formatter.date_huge) {
    i18n.services.formatter.add('date_huge', (value, lng, options) => {
      return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
    });
  }

  return (
    <>
      <Header />
      <div
        style={{
          margin: '0 auto',
          maxWidth: 960,
          padding: '0 1.0875rem 1.45rem',
        }}
      >
        <main>{children}</main>
        <footer style={{ marginTop: 50 }}>
          <i>
            {
              t('footer', { date: new Date(), context: getGreetingTime() })
            }
          </i>
        </footer>
      </div>
    </>
  );
};

export default Layout;
Вход в полноэкранный режим Выйти из полноэкранного режима

И добавим несколько контекстно-специфических клавиш перевода:

locales/en/common.json

{
  "footer": "Today is {{date, date_huge}}",
  "footer_afternoon": "Good afternoon! It's {{date, date_huge}}",
  "footer_evening": "Good evening! Today was the {{date, date_huge}}",
  "footer_morning": "Good morning! Today is {{date, date_huge}} | Have a nice day!"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

locales/de/common.json

{
  "footer": "Heute ist {{date, date_huge}}",
  "footer_afternoon": "Guten Nachmittag! Es ist {{date, date_huge}}",
  "footer_evening": "Guten Abend! Heute war der {{date, date_huge}}",
  "footer_morning": "Guten Morgen! Heute ist {{date, date_huge}} | Einen schönen Tag noch!"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

😁 Да, это работает!

Извлечение ключей

Благодаря babel-plugin-i18next-extract вы можете автоматически извлекать переводы внутри функции t и компонента Trans из ваших страниц и сохранять их в файлах пространства имен.

Это работает следующим образом:

Сначала установите необходимые зависимости:

npm install @babel/cli @babel/plugin-transform-typescript babel-plugin-i18next-extract

Создайте или обновите файл babel-extract.config.js (НЕ называйте его babel.config.js, иначе он будет использоваться gatsby):

const { defaultLanguage } = require('./languages');
process.env.NODE_ENV = 'test';
module.exports = {
  presets: ['babel-preset-gatsby'],
  plugins: [
    [
      'i18next-extract',
      {
        keyAsDefaultValue: [defaultLanguage],
        useI18nextDefaultValue: [defaultLanguage],
        // discardOldKeys: true,
        defaultNS: 'common',
        outputPath: 'locales/{{locale}}/{{ns}}.json',
        customTransComponents: [['gatsby-plugin-react-i18next', 'Trans']],
        compatibilityJSON: 'v4',
      }
    ]
  ],
  overrides: [
    {
      test: [`**/*.ts`, `**/*.tsx`],
      plugins: [[`@babel/plugin-transform-typescript`, {isTSX: true}]]
    }
  ]
};
Войдите в полноэкранный режим Выйти из полноэкранного режима

Добавьте скрипт в ваш package.json:

"scripts": {
  "extract": "babel --config-file ./babel-extract.config.js -o tmp/chunk.js 'src/**/*.{js,jsx,ts,tsx}' && rm -rf tmp"
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы хотите извлечь переводы на страницу для определенного пространства имен, вы можете добавить специальный комментарий в начале страницы:

// i18next-extract-mark-ns-start index

import React from 'react';
// ...
Войти в полноэкранный режим Выйти из полноэкранного режима

fyi: Существуют и другие подсказки к комментариям, которые вы можете использовать.

Подготовили все свои страницы? Отлично, давайте попробуем:

// i18next-extract-mark-ns-start index

import React from 'react';
import { Link, Trans, useTranslation } from 'gatsby-plugin-react-i18next';
import { graphql, Link as GatsbyLink } from 'gatsby';
import { StaticImage } from 'gatsby-plugin-image';
import Layout from '../components/layout';
import Seo from '../components/seo';

const IndexPage = () => {
  const { t } = useTranslation();
  return (
    <Layout>
      <Seo title={t('seo')} />
      <h1>
        <Trans i18nKey="title">Hi people</Trans>
      </h1>
      <p>
        <Trans i18nKey="welcome">Welcome to your new Gatsby site.</Trans>
      </p>
      <p>
        <Trans i18nKey="cta">Now go build something great.</Trans>
      </p>
      <p>
        <Link to="/page-2/">
          <Trans i18nKey="goToPage2">Go to page 2</Trans>
        </Link>
      </p>
    </Layout>
  );
};

export default IndexPage;

export const query = graphql`
  query ($language: String!) {
    locales: allLocale(
      filter: { ns: { in: ["common", "index"] }, language: { eq: $language } }
    ) {
      edges {
        node {
          ns
          data
          language
        }
      }
    }
  }
`;
Войти в полноэкранный режим Выход из полноэкранного режима

Запуск npm run extract теперь добавит этот новый ключ cta в файл пространства имен:

{
  "cta": "Now go build something great.",
  "goToPage2": "Go to page 2",
  "seo": "Home",
  "title": "Hi people",
  "welcome": "Welcome to your new Gatsby site."
}
Вход в полноэкранный режим Выход из полноэкранного режима

Дополнительная мощность

Все это уже замечательно, но мы можем сделать еще больше!

Было бы неплохо иметь обзор, показывающий, какие переводы отсутствуют, а какие файлы переведены полностью…

И подумайте о том, чтобы при извлечении новых ключей они автоматически переводились?

Чтобы это стало реальностью, нам нужен менеджмент переводов…

Посылая переводы некоторым переводчикам или бюро переводов, вы получаете больше контроля и прямой контакт с ними. Но это также означает больше работы для вас.
Это традиционный способ. Но имейте в виду, что пересылка файлов всегда сопряжена с накладными расходами.

Существует ли лучший вариант?

Конечно!

i18next помогает перевести приложение, и это замечательно, но есть и другие варианты.

  • Как вы интегрируете любые переводческие службы / агентства?
  • Как вы отслеживаете новый или удаленный контент?
  • Как вы обеспечиваете надлежащее версионирование?
  • и многое другое…

Ищу что-то вроде этого❓

  • Легко интегрировать
  • Непрерывное развертывание? Непрерывная локализация!
  • Легко управляйте файлами переводов
  • Заказывайте профессиональные переводы
  • Аналитика и статистика
  • Версионирование ваших переводов
  • Автоматический и машинный перевод по требованию
  • Без риска: Возьмите свои данные с собой
  • Прозрачное и справедливое ценообразование
  • и многое другое…

Как это выглядит?

Сначала вам нужно зарегистрироваться в locize и войти в систему.
Затем создать новый проект в locize и добавить все необходимые языки. И наконец, вы можете добавить свои переводы либо с помощью cli, либо импортировав отдельные json-файлы, либо через API.

Теперь давайте установим locize-cli:

npm install -g locize-cli

Мы подготовим новый скрипт, который будет синхронизировать наши локальные изменения с locize.
А также необязательный второй скрипт, который будет просто загружать новейшие переводы из locize.
Убедитесь, что вы используете свой project-id и api-key:

"scripts": {
  "syncLocales": "locize sync --project-id=5d47a999-5c34-4161-a389-bc2189507a50 --ver=latest --api-key=42ca9d58-18da-44c7-8dd3-8f59b8c35bda --path=./locales",
  "downloadLocales": "locize download --project-id=5d47a999-5c34-4161-a389-bc2189507a50 --ver=latest --clean=true --path=./locales"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Используйте скрипт npm run syncLocales для синхронизации вашего локального репозитория с тем, что опубликовано на locize.

В качестве альтернативы вы также можете использовать скрипт npm run downloadLocales, чтобы всегда загружать опубликованные переводы locize в ваш локальный репозиторий перед сборкой вашего приложения.

Если теперь мы добавим новый ключ перевода, например, такой:

<Trans i18nKey="newKey">this will be added automatically after "extract" and "syncLocales"</Trans>
Войти в полноэкранный режим Выйти из полноэкранного режима

и запустить npm run export, а затем npm run syncLocales, мы получим следующее:

locales/en/page-2.json:

{
  "back": "Go back to the homepage",
  "counter_one": "clicked one time",
  "counter_other": "clicked {{count}} time",
  "counter_zero": "Click me!",
  "title": "Page two",
  "welcome": "Welcome to page 2",
  "newKey": "this will be added automatically after "extract" and "syncLocales""
}
Вход в полноэкранный режим Выход из полноэкранного режима

locales/de/page-2.json:

{
  "back": "Gehen Sie zurück zur Startseite",
  "counter_one": "einmal angeklickt",
  "counter_other": "{{count}} Mal geklickt",
  "counter_zero": "Klick mich!",
  "title": "Seite zwei",
  "welcome": "Willkommen auf Seite 2",
  "newKey": "dies wird automatisch nach "extract" und "syncLocales" hinzugefügt"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Благодаря опционально включенной опции автоматического машинного перевода новые ключи не только добавляются в locize в процессе разработки приложения, но и автоматически переводятся на целевые языки с помощью машинного перевода.

👀 но это еще не все… (InContext Editor)

С помощью плагина locize вы сможете использовать свое приложение в InContext Editor от locize.

Хотите посмотреть, как это выглядит?

Итак, сначала установите зависимость locize:

npm install locize

Затем в коде (выбираем наш файл layout.js) добавьте следующее:

import React from 'react';
import { useI18next } from 'gatsby-plugin-react-i18next';
import { locizePlugin, setEditorLng } from 'locize';
// ...

const Layout = ({ children }) => {
  const { t, i18n } = useI18next();

  // defining custom formatters is normally done immediately after the i18next.init call, but with gatsby-plugin-react-i18next is not possible, so let's add it here
  if (!i18n.services.formatter.date_huge) {
    i18n.services.formatter.add('date_huge', (value, lng, options) => {
      return DateTime.fromJSDate(value).setLocale(lng).toLocaleString(DateTime.DATE_HUGE)
    });
    // also the locize plugin normally is automatically configured, but here we need to do it that way
    locizePlugin.init(i18n);
    setEditorLng(i18n.resolvedLanguage);
  }

  return (
    <>
      <Header />
      <div
        style={{
          margin: '0 auto',
          maxWidth: 960,
          padding: '0 1.0875rem 1.45rem',
        }}
      >
        <main>{children}</main>
        <footer style={{ marginTop: 50 }}>
          <i>
            {
              t('footer', { date: new Date(), context: getGreetingTime() })
            }
          </i>
        </footer>
      </div>
    </>
  );
};

export default Layout;
Войти в полноэкранный режим Выйти из полноэкранного режима

И в gatsby-config.js добавьте несколько новых опций react:

const { languages, defaultLanguage } = require('./languages');
module.exports = {
  // ...
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/locales`,
        name: `locale`
      }
    },
    {
      resolve: 'gatsby-plugin-react-i18next',
      options: {
        languages,
        defaultLanguage,
        siteUrl,
        i18nextOptions: {
          // debug: true,
          fallbackLng: defaultLanguage,
          supportedLngs: languages,
          defaultNS: 'common',
          interpolation: {
            escapeValue: false, // not needed for react as it escapes by default
          },
          react: {
            bindI18n: 'languageChanged editorSaved', // the editorSaved event will trigger a rerender
          }
        },
      },
    },
    // ...
  ]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем перейдите в ваш проект locize и определите урлы вашего контекстного редактора, как описано здесь.

Результат будет выглядеть следующим образом:

Разве это не здорово?

🧑💻 Полный код можно найти здесь.

Если вы хотите узнать больше об основах i18next, есть также видео «i18next crash course».

🎉🥳 Поздравляю 🎊🎁.

Надеюсь, вы узнали несколько новых вещей о gatsby-plugin-react-i18next, i18next, локализации React.js и современных рабочих процессах локализации.

Итак, если вы хотите вывести тему i18n на новый уровень, стоит попробовать платформу управления локализацией — locize.

Основатели locize также являются создателями i18next. Таким образом, используя locize, вы напрямую поддерживаете будущее i18next.

👍

Оцените статью
devanswers.ru
Добавить комментарий