source

반응/축소 및 다국어(국제화) 애플리케이션 - 아키텍처

factcode 2023. 3. 25. 11:57
반응형

반응/축소 및 다국어(국제화) 애플리케이션 - 아키텍처

저는 다국어 및 로컬로 이용할 수 있는 앱을 만들고 있습니다.

제 질문은 단순히 기술적인 것이 아니라 아키텍처와 사람들이 실제로 이 문제를 해결하기 위해 사용하는 패턴에 관한 것입니다.어디에도 '쿡북'을 찾을 수 없어서 좋아하는 Q/A 사이트를 찾아봅니다.

요건은 다음과 같습니다(실제로 '표준'입니다.

  • 사용자는 언어를 선택할 수 있습니다(중요).
  • 언어를 변경하면 인터페이스가 새로운 선택된 언어로 자동 번역됩니다.
  • 지금은 숫자나 날짜 등의 형식에 크게 신경 쓰지 않고 문자열만 번역할 수 있는 간단한 솔루션을 원합니다.

생각할 수 있는 솔루션은 다음과 같습니다.

각 컴포넌트는 번역을 개별적으로 처리합니다.

즉, 각 컴포넌트에는 예를 들어 en.json, fr.json 등의 파일세트와 변환된 문자열이 포함되어 있습니다.그리고 선택한 언어에 따라 값을 읽을 수 있는 도우미 기능도 있습니다.

  • 장점: 각 컴포넌트는 '스탠드 아론'이라는 리액트 원칙을 존중합니다.
  • 단점: 모든 번역을 파일에 집중시킬 수 없습니다(다른 사용자에게 새로운 언어를 추가하도록 지시하는 등).
  • 단점: 모든 피비린내 나는 컴포넌트와 그 자녀에게 현재의 언어를 전달해야 합니다.

각 컴포넌트는 소품을 통해 번역을 받습니다.

그래서 그들은 현재의 언어를 모르고 현악기 목록을 소품으로 가져가서 현악기와 일치한다.

  • 찬성: 이러한 문자열은 "상부에서" 나오기 때문에 중앙집중화할 수 있습니다.
  • 단점: 각 컴포넌트가 번역 시스템에 연결되어 있습니다.하나의 컴포넌트를 재사용할 수 없습니다.매번 올바른 문자열을 지정해야 합니다.

소품들을 조금 건너뛰고, 어쩌면 문맥을 이용해서 현재의 언어를 전승할 수도 있습니다.

  • 장점: 투과성이 높기 때문에 항상 소품을 통해 현재의 언어나 번역을 전달할 필요가 없습니다.
  • 단점: 사용하기 번거로워 보인다

다른 아이디어가 있으면 꼭 말해주세요!

어떻게 하는 거야?

몇 가지 솔루션을 시험해 본 결과, 리액트 0.14(믹스인을 사용하지 않고 고차 컴포넌트)에 적합한 솔루션을 찾을 수 있었습니다(편집: 물론 리액트 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 혼합을 대체합니다).

첫 번째 인수는 변환 파일 내의 변환을 검색하기 위해 사용되는 키입니다(여기서는 컴포넌트 이름을 사용했는데, 어떤 것이든 상관없습니다).두 번째 기능(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;
    };
}

이것은 마법이 아닙니다. 컨텍스트에서 현재 언어를 읽고(그리고 이 컨텍스트는 코드 베이스 전체에 걸쳐서 블리딩되지 않고, 이 래퍼에서 사용됨), 로드된 파일에서 관련 문자열 개체를 가져옵니다.이 예에서는 이 논리가 매우 순진하며, 실제로 원하는 방식으로 수행될 수 있습니다.

중요한 점은 제공된 키를 사용하여 컨텍스트에서 현재 언어를 가져와 문자열로 변환한다는 것입니다.

계층의 맨 위에 있다.

루트 컴포넌트에서는 현재 상태에서 현재 언어를 설정하기만 하면 됩니다.다음 예시는 Redx를 플럭스와 유사한 구현으로 사용하고 있지만 다른 프레임워크/패턴/라이브러리를 사용하여 쉽게 변환할 수 있습니다.

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'
    }
};

여러분 어떻게 생각하세요?

이것으로 질문에서 회피하려고 했던 모든 문제를 해결할 수 있을 것 같습니다.번역 로직이 소스 코드 전체에 흐르지 않고 매우 고립되어 컴포넌트를 사용하지 않고도 재사용할 수 있습니다.

들어 는 translate수 에 "My Component" translate()를 모든 가 재사용할 수 있습니다strings들만그

[편집: 2016년 3월 31일]:저는 최근에 React & Redux로 구축된 Retrospective Board(애자일 레트로스펙티브용)에서 다국어를 구사하고 있습니다.댓글에 실전 사례를 묻는 사람이 꽤 많았기 때문에, 이하와 같다.

코드는 https://github.com/antoinejaussoin/retro-board/tree/master 에서 찾을 수 있습니다.

제 경험상 최선의 방법은 i18n 리덕스 상태를 생성하여 여러 가지 이유로 사용하는 것입니다.

1- 데이터베이스, 로컬 파일 또는 EJS나 Jade 등의 템플릿 엔진에서 초기값을 전달할 수 있습니다.

2- 사용자가 언어를 변경하면 UI를 새로 고치지 않고도 전체 응용 프로그램 언어를 변경할 수 있습니다.

3-사용자가 언어를 변경하면 API, 로컬 파일 또는 상수에서 새로운 언어를 검색할 수도 있습니다.

4 - 시간대, 통화, 방향(RTL/LTR) 및 사용 가능한 언어 목록 등의 문자열을 사용하여 다른 중요한 내용을 저장할 수도 있습니다.

5- 언어 변경은 통상적인 리덕스 액션으로 정의할 수 있습니다.

6 - 백엔드와 프런트 엔드 스트링을 한 곳에 저장할 수 있습니다.예를 들어, 저는 현지화에 i18n-node를 사용하고 있습니다.사용자가 UI 언어를 변경하면 일반 API 호출을 실행하고 백엔드에서 반환하기만 하면 됩니다.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-문자열 정렬을 사용하면 다음과 같이 카탈로그 문자열 사이에 값을 삽입할 수 있습니다.

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

2-인간 형식 이 모듈을 사용하면 다음과 같이 사람이 읽을 수 있는 문자열에서 숫자를 변환할 수 있습니다.

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

가장 유명한 날짜 및 시간 npm 라이브러리에서는 모멘트를 번역할 수 있지만 현재 상태 언어를 전달해야 합니다.예를 들어 다음과 같습니다.

import moment from "moment";

const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م

갱신(14/06/2019)

현재 리액트 컨텍스트 API(redux 없음)를 사용하여 동일한 개념을 구현한 프레임워크가 많이 있으며 I18next를 개인적으로 추천했습니다.

Antoine의 솔루션은 잘 작동하지만 몇 가지 주의사항이 있습니다.

  • React 콘텍스트를 직접 사용하지만, 이미 Redux를 사용할 때는 피하고 싶은 경향이 있습니다.
  • 파일로부터 직접 구문을 Import 합니다.실행시 클라이언트 측에서 필요한 언어를 가져오려면 문제가 발생할 수 있습니다.
  • 경량인 i18n 라이브러리를 사용하지 않지만 다중화 및 보간과 같은 편리한 번역 기능에 액세스할 수 없습니다.

그래서 Redux와 AirBNB의 Polyglot레독스 폴리글롯을 구축했습니다.
중 한 입니다.)

다음과 같은 기능을 제공합니다.

  • 언어 및 해당 메시지를 Redux 저장소에 저장하는 리듀서입니다.다음 중 하나를 사용하여 둘 다 제공할 수 있습니다.
    • 특정 액션을 포착하고 현재 언어를 차감하며 관련 메시지를 가져오거나 가져오도록 구성할 수 있는 미들웨어입니다.
    • setLanguage(lang, messages)
  • a getP(state)「」를 P4번:
    • t(key) 함수 : 리리널널 T 。
    • tc(key) : " " " "
    • tu(key): " " "
    • tm(morphism)(key) : " " " "
  • a getLocale(state) current (현재 언어를 취득하는 셀렉터)
  • a translate을 주입하여 리액트 합니다.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);

질문/제안이 있으시면 알려주세요!

또, 서드파티에 의존하지 않는 ES6, Redux, Hooks, JSON에 근거해, 타입 스크립트로 실장되고 있는 (경량) 제안도 있습니다.

선택한 언어가 redex 상태로 로드되기 때문에 해당 텍스트만 렌더링하지 않아도 언어 변경이 매우 빠릅니다.

파트 1: Redux 셋업:

/src/shared/Types.tsx

export type Language = 'EN' | 'CA';

/src/스토어/액션/액션Types.tx

export const SET_LANGUAGE = 'SET_LANGUAGE';

/src/store/actions/language.tsx:

import * as actionTypes from './actionTypes';
import { Language } from '../../shared/Types';

export const setLanguage = (language: Language) => ({
   type: actionTypes.SET_LANGUAGE,
   language: language,
});

/src/store/inchers/language.tsx:

import * as actionTypes from '../action/actionTypes';
import { Language } from '../../shared/Types';
import { RootState } from './reducer';
import dataEN from '../../locales/en/translation.json';
import dataCA from '../../locales/ca/translation.json';

type rootState = RootState['language'];

interface State extends rootState { }
interface Action extends rootState {
    type: string,
}

const initialState = {
    language: 'EN' as Language,
    data: dataEN,
};

const setLanguage = (state: State, action: Action) => {
    let data;
    switch (action.language) {
        case 'EN':
            data = dataEN;
            break;
        case 'CA':
            data = dataCA;
            break;
        default:
            break;
    }
    return {
        ...state,
        ...{ language: action.language,
             data: data,
            }
    };
};

const reducer = (state = initialState, action: Action) => {
    switch (action.type) {
        case actionTypes.SET_LANGUAGE: return setLanguage(state, action);
        default: return state;
    }
};

export default reducer;

/src/store/cappers/cappercer.tsx

import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { Language } from '../../shared/Types';

export interface RootState {
    language: {
        language: Language,
        data: any,
    }
};

export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

/src/App.tsx

import React from 'react';
import { Provider } from 'react-redux';
import { createStore, combineReducers } from 'redux';
import languageReducer from './store/reducers/language';
import styles from './App.module.css';

// Set global state variables through Redux
const rootReducer = combineReducers({
    language: languageReducer,
});
const store = createStore(rootReducer);

const App = () => {

    return (
        <Provider store={store}>
            <div className={styles.App}>
                // Your components
            </div>
        </Provider>
    );
}

export default App;

파트 2: 언어가 포함된 드롭다운 메뉴.제 경우, 이 컴포넌트를 네비게이션바에 배치하여 임의의 화면에서 언어를 변경할 수 있도록 합니다.

/src/컴포넌트/내비게이션/Language.tsx

import React from 'react';
import { useDispatch } from 'react-redux';
import { setLanguage } from '../../store/action/language';
import { useTypedSelector } from '../../store/reducers/reducer';
import { Language as Lang } from '../../shared/Types';
import styles from './Language.module.css';

const Language = () => {
    const dispatch = useDispatch();
    const language = useTypedSelector(state => state.language.language);
    
    return (
        <div>
            <select
                className={styles.Select}
                value={language}
                onChange={e => dispatch(setLanguage(e.currentTarget.value as Lang))}>
                <option value="EN">EN</option>
                <option value="CA">CA</option>
            </select>
        </div>
    );
};

export default Language;

파트 3: JSON 파일이 예에서는 몇 가지 언어를 사용한 테스트 값만 보여 줍니다.

/src/locales/en/information.json

{
    "message": "Welcome"
}

/src/locales/ca/information.json

{
    "message": "Benvinguts"
}

파트 4: 이제 임의의 화면에서 Redux 설정에서 선택한 언어로 텍스트를 표시할 수 있습니다.

import React from 'react';
import { useTypedSelector } from '../../store/reducers/reducer';

const Test = () => {
    const t = useTypedSelector(state => state.language.data);

    return (
        <div> {t.message} </div>
    )
}

export default Test;

투고 연장에 대해서는 죄송합니다만, 모든 의구심을 해소하기 위해 완전한 설정을 보여주려고 했습니다.이 작업이 완료되면 언어를 추가하고 어디서나 설명을 사용할 수 있습니다.

제가 조사한 바로는 자바스크립트에서는 i18n, ICUgettext 두 가지 주요 접근법이 사용되고 있는 것 같습니다.

저는 gettext만 써보니까 편견이 있어요.

나를 놀라게 하는 것은 지원이 얼마나 허술한가 하는 것이다.는 PHP의 는 PHP 、 PressPHP WordPress. 모두 것이입니다.__('')그 후 PO 파일을 사용하여 쉽게 번역할 수 있습니다.

텍스트 취득

string 포맷에 대해 sprintf를 잘 알고 있습니다.PO 파일은 수천 개의 다른 에이전시에 의해 쉽게 번역됩니다.

일반적인 두 가지 옵션이 있습니다.

  1. i18next (사용방법에 대해서는 arkency.com 블로그 투고에서 설명)
  2. Jed, sentry.io의 투고와 이 React+Redux의 투고에 기재되어 있는 사용법에 대해서는,

둘 다 gettext 스타일 지원, 문자열의 sprintf 스타일 포맷 및 PO 파일 Import/export 기능이 있습니다.

i18next에는 자체 개발한 React 확장 기능이 있습니다.제드는 그렇지 않아Sentry.io에서는 Jed와 React의 커스텀 통합을 사용하고 있는 것 같습니다.React+Redux 게시물에서는 다음을 권장합니다.

도구: jed + po2json + jsxgettext

그러나 Jed는 gettext에 초점을 맞춘 구현으로 보입니다.즉, i18 next는 그것을 옵션으로 사용합니다.

ICU

이것은, 성별에 관한 대처 등, 번역에 관한 엣지 케이스의 서포트를 강화합니다.번역할 수 있는 언어가 더 복잡하면 이점을 알 수 있을 것입니다.

일반적인 옵션은 messageformat.js입니다.sentry.io 블로그 튜토리얼에서 간단히 설명했습니다.messageformat.js는 실제로 Jed를 쓴 사람과 같은 사람에 의해 개발되었습니다.는 ICU 사용에 대해 매우 강경한 주장을 펼칩니다.

제드는 기능이 완벽하다고 생각합니다.버그를 수정하는 것은 기쁘지만, 일반적으로 라이브러리에 추가하는 것에는 관심이 없습니다.

message format.js도 유지합니다.gettext 구현이 특별히 필요하지 않다면 대신 MessageFormat을 사용하는 것이 좋습니다. 왜냐하면 MessageFormat은 다양한 성별을 지원하며 로케일 데이터가 내장되어 있기 때문입니다.

대략적인 비교

sprintf를 사용한 gettext:

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'] }
);

message format.message (가이드를 읽고 추측):

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' });

아직 다 보지 않았다면 https://react.i18next.com/을 보는 것이 좋은 조언일 수 있습니다.이것은 i18 next에 근거하고 있습니다.한 번 배우면 모든 곳에서 번역할 수 있습니다.

코드는 다음과 같습니다.

<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>

다음 용도의 샘플이 포함되어 있습니다.

  • 웹 팩
  • 크래
  • expo.2011
  • next.discloss.discloss(다음).
  • 동화책 통합
  • 요란하게 하다
  • dat
  • ...

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

그 외에도 개발 중이나 나중에 번역자를 위한 워크플로우도 고려해야 합니다.-> https://www.youtube.com/watch?v=9NOzJhgmyQE

create-react-app을 사용한 간단한 솔루션을 제안하고 싶습니다.

어플리케이션은 언어별로 개별적으로 구축되므로 번역 로직 전체가 어플리케이션에서 제외됩니다.

웹 서버는 Accept-Language 헤더에 따라 올바른 언어를 자동으로 제공하거나 쿠키를 수동으로 설정합니다.

대부분의 경우 언어를 한 번 이상 바꾸지 않습니다.)

번역 데이터는 스타일, html 및 코드에 따라 동일한 컴포넌트 파일에 저장됩니다.

여기에는 자체 상태, 뷰, 번역을 담당하는 완전히 독립된 컴포넌트가 있습니다.

import React from 'react';
import {withStyles} from 'material-ui/styles';
import {languageForm} from './common-language';
const {REACT_APP_LANGUAGE: LANGUAGE} = process.env;
export let language; // define and export language if you wish
class Component extends React.Component {
    render() {
        return (
            <div className={this.props.classes.someStyle}>
                <h2>{language.title}</h2>
                <p>{language.description}</p>
                <p>{language.amount}</p>
                <button>{languageForm.save}</button>
            </div>
        );
    }
}
const styles = theme => ({
    someStyle: {padding: 10},
});
export default withStyles(styles)(Component);
// sets laguage at build time
language = (
    LANGUAGE === 'ru' ? { // Russian
        title: 'Транзакции',
        description: 'Описание',
        amount: 'Сумма',
    } :
    LANGUAGE === 'ee' ? { // Estonian
        title: 'Tehingud',
        description: 'Kirjeldus',
        amount: 'Summa',
    } :
    { // default language // English
        title: 'Transactions',
        description: 'Description',
        amount: 'Sum',
    }
);

패키지에 언어 환경 변수를 추가합니다.json

"start": "REACT_APP_LANGUAGE=ru npm-run-all -p watch-css start-js",
"build": "REACT_APP_LANGUAGE=ru npm-run-all build-css build-js",

바로 그거야!

또, 당초의 답변에는, 번역 마다 1개의 json 파일을 가지는 보다 일원적인 어프로치가 포함되어 있었습니다.

lang/ru.json

{"hello": "Привет"}

lib/module.module

export default require(`../lang/${process.env.REACT_APP_LANGUAGE}.json`);

src/App.jsx

import lang from '../lib/lang.js';
console.log(lang.hello);

언급URL : https://stackoverflow.com/questions/33413880/react-redux-and-multilingual-internationalization-apps-architecture

반응형