При запуске нового веб-приложения я склонен думать, что интернационализация - это не проблема первой версии. Однако, как только ваш побочный проект начинает превращаться в реальное приложение, ваша пользовательская база растет, и когда вы живете не в англоязычной стране (например, во Франции), вы можете захотеть, чтобы ваше приложение было доступно на английском языке. .

Хотя часто довольно просто справиться с интернационализацией в Symfony или в React самостоятельно, справиться с этим в обоих одновременно оказалось довольно сложной задачей для нас в Windoo.

В этом посте я расскажу о том, как мы поддерживаем единый файл перевода для Symfony и React. Я не буду касаться пользовательской стороны настройки локали, поскольку это не проблема.

Перевод в проекте Symfony

Вам необходимо настроить компонент перевода. Вероятно, вам нужно только потребовать его и сразу же использовать, если вы используете Flex (и, вероятно, вам следует это сделать):

composer require symfony/translation

Если вы используете twig для своих шаблонов (страниц, электронных писем ...), вам нужно будет иметь свои переводы в файле, таком как messages.fr.yml и messages.en.yml, и использовать следующий синтаксис в ваших шаблонах:

<a href="{{ path('booking_list') }}">
    {{ 'common.see_all'|trans }}
</a>

Если вы хотите перевести сообщение в контроллер или в службу, вам необходимо внедрить службу TranslatorInterface:

public function greet(TranslatorInterface $translator): Response
{
    return new Response($translator->trans('common.greet'));
}

В итоге вы получите большой yaml-файл, например:

common:
   post: Post 
   greet: Hello !
   see_all: See all
   hide: Hide
   home: Home
post:
   title: Post title

Таким образом, вы можете использовать ключи Yaml в своем переводе. Вы можете управлять своим локальным компьютером с помощью Symfony, иметь два файла, один на английском, один на французском, и готово!

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

Перевод в проект React

В React есть большая стандартная библиотека с названием react-i18next. После того, как вы его настроили, вы в конечном итоге используете его следующим образом (есть несколько вариантов).

import { useTranslation } from 'react-i18next';
const { t, i18n } = useTranslation();
export default () => <h1>{t('common.greet')}</h1>

И инициализация ваших переводов будет выглядеть так:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
i18next
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        common: {
          greet: 'hello!'
        },
        post: {
          title: 'Post title'
        }
      },
      fr: {
        common: {
          greet: 'Bonjour !'
        },
        post: {
          title: 'Titre d\'article'
        }
      }
    }
  });

Таким образом, вам в основном нужен файл JSON, который можно анализировать в объекте Javascript.

Совместное использование переводов React и Symfony

Теперь все это хорошо, когда вы работаете над React SPA или полнофункциональным проектом Symfony. Но когда вы начинаете делиться своими переводами в обоих проектах, все становится… беспорядочно.

Во-первых, форматы Symfony и React-i18n не совсем совместимы. React-i18n не читает формат xliff, ни yaml… на самом деле ему нужен только объект javascript в стиле JSON в качестве входного файла. Таким образом, это также одна из причин, по которой мы не использовали формат xliff для хранения наших переводов.

Чтобы использовать один и тот же исходный файл для обоих проектов в Windoo, мы решили написать сценарий преобразования формата Symfony на лету, который будет внедрен в проект React. Для этого мы использовали загрузчик yaml для преобразования файлов yaml в объект javascript.

// utils/i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import frYaml from 'js-yaml-loader!./translations/messages.fr.yml';
import enYaml from 'js-yaml-loader!./translations/messages.en.yml';
i18n
  .use(initReactI18next)
  .init({
    resources: {
      fr: {
        translation: frYaml,
      },
      en: {
        translation: enYaml,
      },
    },
    lng: (window && window.locale) || 'fr',
    fallbackLng: 'fr',
  });

export default i18n;

Таким образом, нам нужно обновить только один тип файла (а я предпочитаю формат Yaml, он красивее для просмотра и проще для поиска), и оба проекта могут использовать общие переводы. Все в порядке, правда?

Остерегайтесь множественного числа и параметров!

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

Вот синтаксис response-i18n для множественного числа в словаре ключей:

{
  "key": "item",
  "key_plural": "items",
  "keyWithCount": "{{count}} item",
  "keyWithCount_plural": "{{count}} items"
}

И вот синтаксис Symfony для того же самого:

key: "item|items",
keyWithCount: "%count% item|%count% items"

И я уже не говорю о полном формате для каждого языка. Мы переводим только французский и английский (по крайней мере, на сегодняшний день), поэтому мы продолжали использовать простой синтаксис.

Таким образом, количество ключей в React не то же самое, что затрудняет перенос с Symfony-Yaml на i18n-JS. В идеале мы хотели бы использовать формат ICU для обоих, но я смог найти для этого достаточно документации. Возможно, мы вернемся к этому позже в свое время.

Формат параметра

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

// replace all %param% with {{param}} 
elem = String(elem).replace(/%([^%]+(?=%))%/gi, '{{$1}}');

Затем нам пришлось разделить ключи множественного числа в Symfony

// newObject = {}; -- the new translation dictionary
//while looping on the keys
if (elem.includes('|')) {
  const plural = elem.split('|');
  newObject[key] = plural[0];
  newObject[`${key}_plural`] = plural[1];
}

Таким образом, мы на лету полностью перенесли наши переводы с Symfony на React. Вот полный пример в одном виде:

После этого вы просто импортируете новую службу i18n.js в компоненты React и вуаля!

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