Приложения React / Redux и многоязычные (интернационализация) - Архитектура

91

Я создаю приложение, которое должно быть доступно на нескольких языках и локалях.

Мой вопрос не чисто технический, а скорее о архитектуре и шаблонах, которые люди фактически используют в производстве для решения этой проблемы. Я не мог найти ни одной "кулинарной книги" для этого, поэтому я обращаюсь к своему любимому сайту Q/A:)

Вот мои требования (они действительно "стандартные" ):

  • Пользователь может выбрать язык (тривиальный)
  • При изменении языка интерфейс должен автоматически переводиться на новый выбранный язык.
  • Я не слишком беспокоюсь о форматировании чисел, дат и т.д. на данный момент, я хочу простое решение просто перевести строки

Вот возможные решения, которые я мог бы подумать:

Каждый компонент имеет дело с переводом в изоляции

Это означает, что каждый компонент имеет, например, набор файлов en.json, fr.json и т.д. вместе с переведенными строками. И вспомогательная функция, помогающая считывать значения от тех, которые зависят от выбранного языка.

  • Pro: более уважительно из философии React, каждый компонент является "автономным".
  • Минусы: вы не можете централизовать все переводы в файле (например, чтобы кто-то добавил новый язык)
  • Минусы: вам все равно нужно передать текущий язык в качестве опоры, в каждом кровавом компоненте и их детях.

Каждый компонент получает переводы через реквизит

Таким образом, они не знают о текущем языке, они просто берут список строк как реквизиты, которые соответствуют текущему языку

  • Pro: поскольку эти строки идут "сверху", они могут быть централизованы где-то
  • Минусы: каждый компонент теперь привязан к системе перевода, вы не можете просто повторно использовать его, вам нужно указывать правильные строки каждый раз

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

  • Pro: он в основном прозрачный, не нужно передавать текущий язык и/или переводы через реквизиты все время
  • Минусы: он выглядит громоздким для использования

Если у вас есть другая идея, пожалуйста, скажите!

Как вы это делаете?

  • 2
    Я предпочитаю идею объекта ключей со строками перевода, который передается как опора, вам не нужно передавать каждую строку как опору в отдельности. Изменение этого на верхнем уровне должно вызвать повторную визуализацию. Я не думаю, что использование контекста является хорошей идеей для этого, и каждый компонент, имеющий доступ к файлу перевода, делает их менее «тупыми» и переносимыми на самом деле imo (и труднее заставить приложение перерисовываться при смене языка).
  • 0
    как насчет центрального магазина, в котором хранится язык? и миксин на ваших компонентах, который тянет это и смотрит на spcified язык?
Показать ещё 10 комментариев
Теги:
architecture
translation
redux
internationalization

5 ответов

92
Лучший ответ

Попробовав несколько решений, я думаю, что нашел тот, который хорошо работает и должен быть идиоматическим решением для React 0.14 (т.е. он не использует mixins, а компоненты более высокого порядка) (редактирование: также отлично работает с React 15 конечно!).

Итак, здесь решение, начинающееся снизу (отдельные компоненты):

Компонент

Единственное, что понадобится вашему компоненту (по соглашению), - это реквизиты strings. Он должен быть объектом, содержащим различные строки, которые вам нужны Компоненты, но в действительности их форма зависит от вас.

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

import { default as React, PropTypes } from 'react';
import translate from './translate';

class MyComponent extends React.Component {
    render() {

        return (
             <div>
                { this.props.strings.someTranslatedText }
             </div>
        );
    }
}

MyComponent.propTypes = {
    strings: PropTypes.object
};

MyComponent.defaultProps = {
     strings: {
         someTranslatedText: 'Hello World'
    }
};

export default translate('MyComponent')(MyComponent);

Компонент более высокого порядка

В предыдущем фрагменте вы могли заметить это на последней строке: translate('MyComponent')(MyComponent)

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

Первый аргумент - это ключ, который будет использоваться для поиска переводов в файле перевода (я использовал здесь имя компонента, но это могло быть что угодно). Второй (обратите внимание, что функция curryed, чтобы позволить декораторам ES7) - сам Компонент обернуть.

Вот код для компонента перевода:

import { default as React } from 'react';
import en from '../i18n/en';
import fr from '../i18n/fr';

const languages = {
    en,
    fr
};

export default function translate(key) {
    return Component => {
        class TranslationComponent extends React.Component {
            render() {
                console.log('current language: ', this.context.currentLanguage);
                var strings = languages[this.context.currentLanguage][key];
                return <Component {...this.props} {...this.state} strings={strings} />;
            }
        }

        TranslationComponent.contextTypes = {
            currentLanguage: React.PropTypes.string
        };

        return TranslationComponent;
    };
}

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

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

На самой вершине иерархии

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

import { default as React, PropTypes } from 'react';
import Menu from '../components/Menu';
import { connect } from 'react-redux';
import { changeLanguage } from '../state/lang';

class App extends React.Component {
    render() {
        return (
            <div>
                <Menu onLanguageChange={this.props.changeLanguage}/>
                <div className="">
                    {this.props.children}
                </div>

            </div>

        );
    }

    getChildContext() {
        return {
            currentLanguage: this.props.currentLanguage
        };
    }
}

App.propTypes = {
    children: PropTypes.object.isRequired,
};

App.childContextTypes = {
    currentLanguage: PropTypes.string.isRequired
};

function select(state){
    return {user: state.auth.user, currentLanguage: state.lang.current};
}

function mapDispatchToProps(dispatch){
    return {
        changeLanguage: (lang) => dispatch(changeLanguage(lang))
    };
}

export default connect(select, mapDispatchToProps)(App);

И чтобы закончить, файлы перевода:

Файлы переводов

// en.js
export default {
    MyComponent: {
        someTranslatedText: 'Hello World'
    },
    SomeOtherComponent: {
        foo: 'bar'
    }
};

// fr.js
export default {
    MyComponent: {
        someTranslatedText: 'Salut le monde'
    },
    SomeOtherComponent: {
        foo: 'bar mais en français'
    }
};

Что вы, ребята, думаете?

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

Например, MyComponent не нужно обертывать translate() и может быть раздельным, позволяя повторному использованию другим, желающим предоставить strings своим собственным значением.

[Редактировать: 31/03/2016]: Недавно я работал над Ретроспективным советом (для Agile Retrospectives), созданным с помощью React и Redux, и многоязычным. Поскольку в комментариях довольно много людей задавали реальный пример, вот он:

Код можно найти здесь: https://github.com/antoinejaussoin/retro-board/tree/master

  • 0
    Это классное решение .. интересно, если вы все еще на связи с этим через несколько месяцев? Я не нашел много советов в отношении советов по шаблонам для этого онлайн
  • 2
    Я на самом деле, я обнаружил, что это работает идеально (для моих нужд). Это заставляет компонент работать без перевода по умолчанию, и перевод просто идет поверх него, и компонент не знает об этом
Показать ещё 14 комментариев
9

Из моего опыта лучший подход заключается в создании состояния i18n redux и использования его по многим причинам:

1- Это позволит вам передать начальное значение из базы данных, локального файла или даже из механизма шаблона, такого как ejs или jade

2- Когда пользователь меняет язык, вы можете изменить весь язык приложения, даже не обновляя интерфейс.

3- Когда пользователь меняет язык, это также позволит вам извлечь новый язык из api, локального файла или даже из констант

4- Вы также можете сохранить другие важные вещи со строками, такими как часовой пояс, валюта, направление (RTL/LTR) и список доступных языков

5- Вы можете определить язык изменений как нормальное действие сокращения

6- Вы можете иметь свои внутренние и внешние строки в одном месте, например, в моем случае я использую i18n-node для локализации и когда пользователь меняет язык ui, я просто делаю обычный вызов api, а в backend я возвращаю i18n.getCatalog(req), это вернет все пользовательские строки только для текущего языка

Мое предложение для начального состояния i18n:

{
  "language":"ar",
  "availableLanguages":[
    {"code":"en","name": "English"},
    {"code":"ar","name":"عربي"}
  ],
  "catalog":[
     "Hello":"مرحباً",
     "Thank You":"شكراً",
     "You have {count} new messages":"لديك {count} رسائل جديدة"
   ],
  "timezone":"",
  "currency":"",
  "direction":"rtl",
}

Дополнительные полезные модули для i18n:

1- string-template, это позволит вам вводить значения между строками вашего каталога, например:

import template from "string-template";
const count = 7;
//....
template(i18n.catalog["You have {count} new messages"],{count}) // لديك ٧ رسائل جديدة

2- human-format этот модуль позволит вам преобразовать число в/из удобочитаемой строки, например:

import humanFormat from "human-format";
//...
humanFormat(1337); // => '1.34 k'
// you can pass your own translated scale, e.g: humanFormat(1337,MyScale)

3- momentjs самые известные даты и время npm-библиотеки, вы можете перевести момент, но у него уже есть встроенный перевод просто вам необходимо передать текущий язык состояния, например:

import moment from "moment";

const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م
  • 0
    Будет ли этот подход работать для более чем двух языков? Учитывая настройку каталога
  • 0
    это должно работать для любого количества языков
Показать ещё 2 комментария
4

Решение антуан работает отлично, но есть некоторые оговорки:

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

Вот почему мы построили redux-polyglot поверх Redux и AirBNB Polyglot.
(Я один из авторов)

Он обеспечивает:

  • редуктор для хранения языка и соответствующих сообщений в вашем магазине Redux. Вы можете предоставить оба:
    • промежуточное программное обеспечение, которое вы можете настроить, чтобы поймать определенное действие, вычесть текущий язык и получить/получить связанные сообщения.
    • прямая отправка setLanguage(lang, messages)
  • a getP(state), который извлекает объект P, который предоставляет 4 метода:
    • t(key): оригинальная функция многоугольника T
    • tc(key): капитализированный перевод
    • tu(key): перевод с верхним расположением
    • tm(morphism)(key): персонализированный переворот в переводе
  • a getLocale(state) селектор для получения текущего языка
  • a translate компонент более высокого порядка для улучшения ваших компонентов React, введя объект P в реквизитах

Пример простого использования:

отправить новый язык:

import setLanguage from 'redux-polyglot/setLanguage';

store.dispatch(setLanguage('en', {
    common: { hello_world: 'Hello world' } } }
}));

в компоненте:

import React, { PropTypes } from 'react';
import translate from 'redux-polyglot/translate';

const MyComponent = props => (
  <div className='someId'>
    {props.p.t('common.hello_world')}
  </div>
);
MyComponent.propTypes = {
  p: PropTypes.shape({t: PropTypes.func.isRequired}).isRequired,
}
export default translate(MyComponent);

Скажите, пожалуйста, если у вас есть вопрос/предложение!

  • 1
    Гораздо лучше оригинальные фразы для перевода. И создать инструмент, который анализирует все компоненты для функций _() например, чтобы получить все эти строки. Таким образом, вы можете перевести это в языковой файл проще и не связываться с сумасшедшими переменными. В некоторых случаях целевые страницы нуждаются в определенной части макета, чтобы отображаться по-разному. Таким образом, должна быть доступна некоторая умная функция выбора по умолчанию против других возможных вариантов.
  • 0
    Привет @Jalil, есть ли где-нибудь полный пример с промежуточным ПО?
Показать ещё 2 комментария
2

Из моих исследований в этом, как представляется, существуют два основных подхода, которые используются для i18n в JavaScript: ICU и gettext.

Я только использовал gettext, поэтому я смещен.

Что меня поражает, так это бедная поддержка. Я родом из мира PHP, CakePHP или WordPress. В обеих этих ситуациях базовым стандартом является то, что все строки просто окружены __(''), а затем вниз по линии вы легко получаете переводы с помощью файлов PO.

Gettext

Вы получаете информацию о sprintf для форматирования строк, а файлы PO будут легко переведены тысячами различных агентств.

Есть два популярных варианта:

Оба имеют поддержку стиля gettext, форматирование строк в стиле sprintf и импорт/экспорт в файлы PO.

i18next имеет React extension, разработанный ими самим. Джед не делает. Sentry.io, похоже, использует пользовательскую интеграцию Jed с React. React + Redux post, предлагает использовать

Инструменты: jed + po2json + jsxgettext

Однако Jed кажется более ориентированной на контекстно-зависимую реализацию - то есть она выражает намерение, где, поскольку i18next просто имеет это как вариант.

ICU

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

Популярным вариантом для этого является messageformat.js. Вкратце обсужден этот учебник по блогу sentry.io. messageformat.js фактически разработан тем же человеком, который написал Jed. Он предъявляет довольно жесткие претензии в отношении использования ICU:

Jed - это функция, на мой взгляд. Я рад исправить ошибки, но, как правило, мне не интересно добавлять больше в библиотеку.

Я также поддерживаю messageformat.js. Если вам не нужна конкретная реализация gettext, я могу предложить вместо этого использовать MessageFormat, так как она лучше поддерживает множественные числа/пол и имеет встроенные данные локали.

Грубое сравнение

gettext с помощью sprintf:

i18next.t('Hello world!');
i18next.t(
    'The first 4 letters of the english alphabet are: %s, %s, %s and %s', 
    { postProcess: 'sprintf', sprintf: ['a', 'b', 'c', 'd'] }
);

messageformat.js(мое лучшее предположение от чтения guide):

mf.compile('Hello world!')();
mf.compile(
    'The first 4 letters of the english alphabet are: {s1}, {s2}, {s3} and {s4}'
)({ s1: 'a', s2: 'b', s3: 'c', s4: 'd' });
  • 0
    Вниз проголосовал. Это не отвечает на вопрос. ОП попросил идею архитектуры, а не предложение или сравнение какой-либо библиотеки i18n.
  • 0
    @TrungDQ Вот что спросил ОП: «Мой вопрос не чисто технический, а скорее об архитектуре и шаблонах, которые люди фактически используют в производстве для решения этой проблемы». , Это две модели, которые используются в производстве.
Показать ещё 2 комментария
0

Если еще не сделано, посмотрев https://react.i18next.com/, это может быть хорошим советом. Он основан на i18next: узнайте один раз - переводите всюду.

Ваш код будет выглядеть примерно так:

<div>{t('simpleContent')}</div>
<Trans i18nKey="userMessagesUnread" count={count}>
  Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>

Поставляется с образцами для:

  • WebPack
  • CRA
  • expo.js
  • next.js
  • интеграция сборника рассказов
  • кутерьмы
  • Дат
  • ...

https://github.com/i18next/react-i18next/tree/master/example

Кроме того, вы должны также рассмотреть рабочий процесс во время разработки, а затем для своих переводчиков → https://www.youtube.com/watch?v=9NOzJhgmyQE

  • 0
    Это не отвечает на вопрос. ОП попросил идею архитектуры, а не предложение или сравнение какой-либо библиотеки i18n.
  • 0
    @TrungDQ, как с вашим комментарием к моему ответу, который вы отклонили - ОП попросил текущие решения, используемые в производстве. Однако я предложил i18next в своем ответе еще в феврале.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню