แอป React / Redux และหลายภาษา (Internationalization) - สถาปัตยกรรม


119

ฉันกำลังสร้างแอปที่จะต้องพร้อมใช้งานในหลายภาษาและสถานที่

คำถามของฉันไม่ใช่เรื่องทางเทคนิค แต่เป็นเรื่องของสถาปัตยกรรมและรูปแบบที่ผู้คนใช้ในการผลิตเพื่อแก้ปัญหานี้ ฉันไม่พบ "ตำราอาหาร" ที่ไหนเลยฉันจึงหันไปหาเว็บไซต์ถาม / ตอบที่ฉันชอบ :)

นี่คือข้อกำหนดของฉัน (เป็น "มาตรฐาน" จริงๆ):

  • ผู้ใช้สามารถเลือกภาษา (เล็กน้อย)
  • เมื่อเปลี่ยนภาษาอินเทอร์เฟซควรแปลโดยอัตโนมัติเป็นภาษาที่เลือกใหม่
  • ฉันไม่กังวลเกี่ยวกับการจัดรูปแบบตัวเลขวันที่และอื่น ๆ มากเกินไปในขณะนี้ฉันต้องการวิธีง่ายๆในการแปลสตริง

นี่คือวิธีแก้ปัญหาที่เป็นไปได้ที่ฉันคิดออก:

แต่ละองค์ประกอบจัดการกับการแปลแยกกัน

ซึ่งหมายความว่าแต่ละองค์ประกอบมีตัวอย่างเช่นชุดของไฟล์ en.json, fr.json เป็นต้นควบคู่ไปกับสตริงที่แปลแล้ว และฟังก์ชันตัวช่วยเพื่อช่วยในการอ่านค่าจากค่าที่ขึ้นอยู่กับภาษาที่เลือก

  • Pro: เคารพในปรัชญาการตอบสนองมากขึ้นแต่ละองค์ประกอบเป็นแบบ "สแตนด์อโลน"
  • จุดด้อย: คุณไม่สามารถรวมการแปลทั้งหมดไว้ในไฟล์ได้ (เช่นให้คนอื่นเพิ่มภาษาใหม่)
  • จุดด้อย: คุณยังคงต้องส่งภาษาปัจจุบันเป็นเสาหลักในทุกองค์ประกอบที่เปื้อนเลือดและลูก ๆ ของพวกเขา

แต่ละองค์ประกอบจะรับการแปลผ่านอุปกรณ์ประกอบฉาก

ดังนั้นพวกเขาจึงไม่ทราบภาษาปัจจุบันพวกเขาเพียงแค่ใช้รายการสตริงเป็นอุปกรณ์ประกอบฉากที่ตรงกับภาษาปัจจุบัน

  • Pro: เนื่องจากสตริงเหล่านั้นมาจาก "ด้านบน" จึงสามารถรวมศูนย์ไว้ที่ใดที่หนึ่งได้
  • จุดด้อย: ตอนนี้แต่ละองค์ประกอบเชื่อมโยงเข้ากับระบบการแปลคุณไม่สามารถใช้ซ้ำได้คุณต้องระบุสตริงที่ถูกต้องทุกครั้ง

คุณหลีกเลี่ยงอุปกรณ์ประกอบฉากเล็กน้อยและอาจใช้บริบทเพื่อส่งผ่านภาษาปัจจุบัน

  • Pro: ส่วนใหญ่โปร่งใสไม่ต้องส่งภาษาปัจจุบันและ / หรือคำแปลผ่านอุปกรณ์ประกอบฉากตลอดเวลา
  • จุดด้อย: มันดูยุ่งยากในการใช้งาน

หากคุณมีความคิดอื่น ๆ โปรดพูด!

คุณจะทำอย่างไรมันได้หรือไม่?


2
ฉันชอบแนวคิดของวัตถุของคีย์ที่มีสตริงการแปลซึ่งส่งต่อเป็นเสาคุณไม่จำเป็นต้องส่งแต่ละสตริงเป็นเสาทีละรายการ การเปลี่ยนค่านี้ที่ระดับบนสุดควรทำให้เกิดการแสดงผลซ้ำ ฉันไม่คิดว่าการใช้บริบทเป็นความคิดที่ดีสำหรับสิ่งนี้และแต่ละองค์ประกอบที่มีการเข้าถึงไฟล์แปลทำให้พวกเขา "โง่" น้อยลงและพกพา imo ได้จริง (และยากที่จะทำให้แอปแสดงผลซ้ำเมื่อเปลี่ยนภาษา)
Dominic

1
ตามจริงแล้วตามfacebook.github.io/react/docs/context.html การใช้บริบทเพื่อแบ่งปันภาษาปัจจุบันเป็นหนึ่งในกรณีการใช้งานที่ถูกต้อง วิธีที่ฉันกำลังพยายามอยู่ตอนนี้คือการใช้สิ่งนี้พร้อมส่วนประกอบคำสั่งซื้อที่สูงขึ้นเพื่อจัดการกับตรรกะของการแยกสตริงสำหรับส่วนประกอบนั้น ๆ (อาจขึ้นอยู่กับคีย์บางตัว)
Antoine Jaussoin

1
บางทีคุณอาจจะยังสามารถใช้ดูที่ทันที พวกเขาจัดการกับปัญหานี้ด้วยวิธีที่แตกต่างไปจากเดิมอย่างสิ้นเชิงโดยจัดการกับปัญหานี้ในส่วนหน้า ala Optimizely (หรือที่เรียกว่าการแก้ไข DOM ขณะโหลด)
Marcel Panse

1
ไม่เลวเลย! มันเป็นสัตว์ร้ายที่แตกต่างกันอย่างสิ้นเชิง (ซึ่งเชื่อมโยงคุณกับบริการที่คุณอาจต้องจ่ายหากเว็บไซต์ของคุณเติบโตขึ้น) แต่ฉันชอบความคิดนี้และมันอาจจะคุ้มค่าสำหรับเว็บไซต์ขนาดเล็กที่คุณต้องทำงานอย่างรวดเร็ว!
Antoine Jaussoin

4
นอกจากนี้คุณอาจต้องการพูดถึงว่าคุณเป็นผู้ร่วมก่อตั้ง Instant แทนที่จะพูดว่า "พวกเขา" ราวกับว่าคุณไม่ได้เกี่ยวข้องอะไรเลย :)
Antoine Jaussoin

คำตอบ:


110

หลังจากลองใช้วิธีแก้ปัญหาไม่กี่วิธีฉันคิดว่าฉันพบวิธีแก้ปัญหาที่ใช้งานได้ดีและน่าจะเป็นวิธีแก้ปัญหาสำหรับ React 0.14 (กล่าวคือไม่ใช้ mixins แต่เป็นส่วนประกอบลำดับที่สูงกว่า) ( แก้ไข : ยังใช้ได้ดีกับ React 15 แน่นอน! )

ดังนั้นที่นี่วิธีแก้ปัญหาโดยเริ่มจากด้านล่าง (ส่วนประกอบแต่ละส่วน):

ส่วนประกอบ

สิ่งเดียวที่คุณต้องการ (ตามแบบแผน) คือstringsอุปกรณ์ประกอบฉาก ควรเป็นวัตถุที่มีสตริงต่างๆที่ต้องการ Component ของคุณ แต่จริงๆแล้วรูปร่างของมันขึ้นอยู่กับคุณ

มันมีการแปลเริ่มต้นดังนั้นคุณสามารถใช้ส่วนประกอบที่อื่นได้โดยไม่จำเป็นต้องให้คำแปลใด ๆ (มันจะทำงานนอกกรอบด้วยภาษาเริ่มต้นภาษาอังกฤษในตัวอย่างนี้)

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

ไม่ใช่เวทมนตร์: มันจะอ่านภาษาปัจจุบันจากบริบท (และบริบทนั้นไม่ได้มีเลือดออกทั่วฐานรหัสเพียงใช้ที่นี่ใน wrapper นี้) จากนั้นรับวัตถุสตริงที่เกี่ยวข้องจากไฟล์ที่โหลด ตรรกะนี้ค่อนข้างไร้เดียงสาในตัวอย่างนี้สามารถทำได้ในแบบที่คุณต้องการจริงๆ

ส่วนที่สำคัญคือใช้ภาษาปัจจุบันจากบริบทและแปลงเป็นสตริงตามคีย์ที่ให้มา

ที่ด้านบนสุดของลำดับชั้น

ในองค์ประกอบรากคุณเพียงแค่ต้องตั้งค่าภาษาปัจจุบันจากสถานะปัจจุบันของคุณ ตัวอย่างต่อไปนี้ใช้ Redux เป็นการนำไปใช้งานแบบ Flux แต่สามารถแปลงได้อย่างง่ายดายโดยใช้กรอบ / รูปแบบ / ไลบรารีอื่น ๆ

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]: ฉันเพิ่งทำงานใน Retrospective Board (สำหรับ Agile Retrospectives) ซึ่งสร้างขึ้นด้วย React & Redux และสามารถพูดได้หลายภาษา เนื่องจากผู้คนจำนวนมากขอตัวอย่างชีวิตจริงในความคิดเห็นนี่คือ:

คุณสามารถค้นหารหัสได้ที่นี่: https://github.com/antoinejaussoin/retro-board/tree/master


นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม .. สงสัยว่าคุณยังอยู่บนเรือหลังจากผ่านไปสองสามเดือนหรือไม่? ฉันไม่พบคำแนะนำมากนักเกี่ยวกับคำแนะนำเกี่ยวกับรูปแบบสำหรับออนไลน์นี้
Damon

2
ที่จริงฉันพบว่ามันทำงานได้อย่างสมบูรณ์แบบ (สำหรับความต้องการของฉัน) มันทำให้ส่วนประกอบทำงานได้โดยไม่ต้องแปลโดยค่าเริ่มต้นและการแปลก็มาอยู่ด้านบนโดยที่ส่วนประกอบไม่ได้รับรู้
Antoine Jaussoin

1
@ l.cetinsoy คุณสามารถใช้ไม้dangerouslySetInnerHTMLค้ำยันได้เพียงแค่คำนึงถึงผลกระทบ (ล้างข้อมูลด้วยตนเอง) ดูfacebook.github.io/react/tips/dangerously-set-inner-html.html
Teodor Sandu

6
มีเหตุผลว่าทำไมคุณถึงไม่ลองใช้ react-intl?
SureshCS

1
ชอบโซลูชันนี้มาก สิ่งหนึ่งที่ฉันเพิ่มซึ่งเราพบว่ามีประโยชน์มากสำหรับความสม่ำเสมอและประหยัดเวลาก็คือถ้าคุณมีส่วนประกอบจำนวนมากที่มีสตริงทั่วไปคุณสามารถใช้ประโยชน์จากตัวแปรและแพร่กระจายไปยังวัตถุเช่นconst formStrings = { cancel, create, required }; export default { fooForm: { ...formStrings, foo: 'foo' }, barForm: { ...formStrings, bar: 'bar' } }
Huw Davies

18

จากประสบการณ์ของฉันแนวทางที่ดีที่สุดคือการสร้างสถานะi18n reduxและใช้งานด้วยเหตุผลหลายประการ:

1- สิ่งนี้จะช่วยให้คุณสามารถส่งผ่านค่าเริ่มต้นจากฐานข้อมูลไฟล์ในเครื่องหรือแม้กระทั่งจากเครื่องมือแม่แบบเช่น EJS หรือหยก

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)

3- ช่วงเวลาที่มีชื่อเสียงที่สุดในห้องสมุด npm คุณสามารถแปลช่วงเวลาได้ แต่มีการแปลในตัวอยู่แล้วเพียงแค่คุณต้องส่งภาษาสถานะปัจจุบันเช่น:

import moment from "moment";

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

ปรับปรุง (14/06/2019)

ปัจจุบันมีเฟรมเวิร์กมากมายที่ใช้แนวคิดเดียวกันโดยใช้ react context API (โดยไม่ต้องใช้ซ้ำ) ฉันแนะนำI18nextเป็นการส่วนตัว


แนวทางนี้ใช้ได้ดีกับมากกว่าสองภาษาหรือไม่ พิจารณาการตั้งค่าแคตตาล็อก
tempranova

โหวตลง สิ่งนี้ไม่ตอบคำถาม OP ขอแนวคิดเกี่ยวกับสถาปัตยกรรมไม่ใช่คำแนะนำหรือเปรียบเทียบไลบรารี i18n ใด ๆ
TrungDQ

9
ฉันแนะนำแคตตาล็อก i18n เป็นสถานะ redux ดูเหมือนว่าคุณไม่เข้าใจ redux
Fareed Alnamrouti

5

วิธีแก้ปัญหาของ Antoine ใช้งานได้ดี แต่มีข้อควรระวัง:

  • มันใช้บริบท React โดยตรงซึ่งฉันมักจะหลีกเลี่ยงเมื่อใช้ Redux อยู่แล้ว
  • นำเข้าวลีโดยตรงจากไฟล์ซึ่งอาจเป็นปัญหาได้หากคุณต้องการดึงภาษาที่ต้องการในรันไทม์ฝั่งไคลเอ็นต์
  • ไม่ได้ใช้ไลบรารี i18n ใด ๆ ซึ่งมีน้ำหนักเบา แต่ไม่ได้ให้คุณเข้าถึงฟังก์ชันการแปลที่สะดวกเช่นการพหูพจน์และการแก้ไข

นั่นเป็นเหตุผลที่เราสร้างRedux-พูดได้หลายภาษาอยู่ด้านบนของทั้งสอง Redux และของ Airbnb พูดได้หลายภาษา
(ฉันเป็นหนึ่งในผู้เขียน)

ให้:

  • ตัวลดการจัดเก็บภาษาและข้อความที่เกี่ยวข้องในร้านค้า Redux ของคุณ คุณสามารถจัดหาทั้งสองอย่างโดย:
    • มิดเดิลแวร์ที่คุณสามารถกำหนดค่าเพื่อตรวจจับการกระทำบางอย่างหักภาษาปัจจุบันและรับ / ดึงข้อความที่เกี่ยวข้อง
    • การจัดส่งโดยตรงของ setLanguage(lang, messages)
  • getP(state)เลือกที่ดึงPวัตถุที่ exposes 4 วิธีการ:
    • t(key): ฟังก์ชัน polyglot T ดั้งเดิม
    • tc(key): การแปลตัวพิมพ์ใหญ่
    • tu(key): การแปลด้านบน
    • tm(morphism)(key): การแปลแปรเปลี่ยนที่กำหนดเอง
  • getLocale(state)เลือกที่จะได้รับภาษาปัจจุบัน
  • 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);

โปรดบอกฉันหากคุณมีคำถาม / ข้อเสนอแนะ!


1
มีการแปลวลีต้นฉบับที่ดีกว่ามาก และเพื่อสร้างเครื่องมือที่แยกวิเคราะห์ส่วนประกอบทั้งหมดสำหรับ_()ฟังก์ชันเพื่อรับสตริงเหล่านั้นทั้งหมด ดังนั้นคุณสามารถแปลไฟล์ภาษาได้ง่ายขึ้นและไม่ยุ่งกับตัวแปรบ้าๆ ในบางกรณีหน้า Landing Page จำเป็นต้องมีการจัดวางบางส่วนเพื่อให้แสดงแตกต่างกัน ดังนั้นควรมีฟังก์ชั่นอัจฉริยะบางอย่างในการเลือกค่าเริ่มต้นเทียบกับตัวเลือกที่เป็นไปได้อื่น ๆ ด้วย
Roman M.Koss

สวัสดี @Jalil มีตัวอย่างที่สมบูรณ์พร้อมมิดเดิลแวร์หรือไม่?
ArkadyB

สวัสดี @ArkadyB เราใช้มันในการผลิตในหลายโครงการที่ไม่ใช่โอเพ่นซอร์ส คุณสามารถค้นหาข้อมูลเพิ่มเติมได้ที่โมดูล README: npmjs.com/package/redux-polyglot คุณมีคำถาม / ปัญหาในการใช้งานหรือไม่?
Jalil

ปัญหาหลักของฉันเกี่ยวกับสิ่งนี้และ polyglot.js คือการสร้างวงล้อขึ้นใหม่ทั้งหมดแทนที่จะสร้างไฟล์ PO ห้องสมุดสำรองนี้มีลักษณะที่มีแนวโน้มnpmjs.com/package/redux-i18n ฉันไม่คิดว่ามันจะแตกต่างไปจากเดิมมากนักมันเป็นเพียงแค่การจัดเตรียมเลเยอร์พิเศษเพื่อแปลงไฟล์ PO
icc97

2

จากการวิจัยของฉันเป็นแบบนี้มีปรากฏเป็นสองแนวทางหลักที่ใช้ในการ i18n ใน JavaScript, ห้องไอซียูและgettext

ฉันเคยใช้ gettext เท่านั้นดังนั้นฉันจึงมีอคติ

สิ่งที่ทำให้ฉันประหลาดใจคือการสนับสนุนนั้นแย่แค่ไหน ฉันมาจากโลก PHP ไม่ว่าจะเป็น CakePHP หรือ WordPress ในทั้งสองสถานการณ์นั้นเป็นมาตรฐานพื้นฐานที่สตริงทั้งหมดจะถูกล้อมรอบจาก__('')นั้นคุณจะได้รับคำแปลโดยใช้ไฟล์ PO ได้อย่างง่ายดาย

gettext

คุณคุ้นเคยกับ sprintf สำหรับการจัดรูปแบบสตริงและไฟล์ PO จะได้รับการแปลอย่างง่ายดายโดยหน่วยงานต่างๆหลายพันแห่ง

มีสองตัวเลือกยอดนิยม:

  1. i18nextพร้อมการใช้งานที่อธิบายไว้ในบล็อกโพสต์ของ arkency.com
  2. Jedกับการใช้งานที่อธิบายโดยโพสต์ sentry.ioและโพสต์React + Reduxนี้

ทั้งสองมีการสนับสนุนสไตล์ gettext การจัดรูปแบบสตริงสไตล์ sprintf และนำเข้า / ส่งออกไปยังไฟล์ PO

i18next มีส่วนขยาย React ที่พัฒนาขึ้นเอง เจดไม่ได้ Sentry.io ดูเหมือนจะใช้การรวม Jed กับ React แบบกำหนดเอง React + Redux โพสต์ , แนะนำให้ใช้

เครื่องมือ: jed + po2json + jsxgettext

อย่างไรก็ตาม Jed ดูเหมือนว่าจะมีการใช้งานที่เน้น gettext มากขึ้นนั่นคือการแสดงเจตนาโดยที่ i18next มีให้เป็นตัวเลือก

ไอซียู

สิ่งนี้ได้รับการสนับสนุนมากขึ้นสำหรับกรณีขอบเกี่ยวกับการแปลเช่นการจัดการกับเพศ ฉันคิดว่าคุณจะเห็นประโยชน์จากสิ่งนี้หากคุณมีภาษาที่ซับซ้อนกว่านี้ในการแปล

ตัวเลือกที่นิยมสำหรับเรื่องนี้คือ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 (เดาได้ดีที่สุดจากการอ่านคู่มือ ):

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

โหวตลง สิ่งนี้ไม่ตอบคำถาม OP ขอแนวคิดเกี่ยวกับสถาปัตยกรรมไม่ใช่คำแนะนำหรือเปรียบเทียบไลบรารี i18n ใด ๆ
TrungDQ

@TrungDQ นี่คือสิ่งที่ OP ถาม: "คำถามของฉันไม่ใช่เรื่องเทคนิค แต่เป็นเรื่องของสถาปัตยกรรมและรูปแบบที่ผู้คนใช้ในการผลิตเพื่อแก้ปัญหานี้" . นี่คือสองรูปแบบที่ใช้ในการผลิต
icc97

ในความคิดของฉันคำตอบนี้ไม่ได้ให้ข้อมูลที่ฉัน (และคนอื่น ๆ ) กำลังมองหา ข้อมูลที่คุณให้มามีประโยชน์ แต่อาจเป็นคำถามอื่น ฉันแค่อยากจะมีส่วนร่วมในการลงคะแนนเพื่อให้คำตอบที่ถูกต้องปรากฏขึ้นที่ด้านบน (ฉันหวังว่า)
TrungDQ

@TrungDQ หากไม่ใช่สิ่งที่คุณกำลังมองหาให้โหวตคะแนนที่คุณเคยใช้และเพิกเฉยต่อคนอื่น ๆ แทนที่จะลงคะแนนคำตอบที่ถูกต้องซึ่งไม่ตรงกับส่วนเฉพาะของคำถามที่คุณสนใจ
icc97

1

หากยังไม่ได้ดูที่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
  • บูรณาการหนังสือนิทาน
  • Razzle
  • ดาด
  • ...

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

นอกจากนั้นคุณควรพิจารณาขั้นตอนการทำงานในระหว่างการพัฒนาและในภายหลังสำหรับนักแปลของคุณ -> https://www.youtube.com/watch?v=9NOzJhgmyQE


สิ่งนี้ไม่ตอบคำถาม OP ขอแนวคิดเกี่ยวกับสถาปัตยกรรมไม่ใช่คำแนะนำหรือเปรียบเทียบไลบรารี i18n ใด ๆ
TrungDQ

@TrungDQ เช่นเดียวกับความคิดเห็นของคุณเกี่ยวกับคำตอบของฉันที่คุณลงคะแนน - OP ขอโซลูชันปัจจุบันที่ใช้ในการผลิต อย่างไรก็ตามฉันได้แนะนำ i18next ในคำตอบของฉันเมื่อย้อนกลับไปในเดือนกุมภาพันธ์
icc97

0

ผมอยากจะนำเสนอวิธีง่ายๆโดยใช้สร้างปฏิกิริยาแอป

แอปพลิเคชันจะสร้างขึ้นสำหรับทุกภาษาแยกกันดังนั้นตรรกะการแปลทั้งหมดจะถูกย้ายออกจากแอปพลิเคชัน

เว็บเซิร์ฟเวอร์จะให้บริการภาษาที่ถูกต้องโดยอัตโนมัติขึ้นอยู่กับการยอมรับภาษาส่วนหัวหรือด้วยตนเองโดยการตั้งค่าคุกกี้

ส่วนใหญ่เราจะไม่เปลี่ยนภาษามากกว่าหนึ่งครั้งถ้าเคยเลย)

ข้อมูลการแปลใส่ไว้ในไฟล์คอมโพเนนต์เดียวกันที่ใช้พร้อมสไตล์ 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',
    }
);

เพิ่มตัวแปรสภาพแวดล้อมภาษาในpackage.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",

อย่างนั้นแหละ!

นอกจากนี้คำตอบเดิมของฉันยังรวมถึงแนวทางเสาหินเพิ่มเติมด้วยไฟล์ json เดียวสำหรับการแปลแต่ละครั้ง:

lang / ru.json

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

lib / lang.js

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

src / App.jsx

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

จะไม่ทำงานเฉพาะเวลารวบรวมหรือไม่? หากไม่มีความสามารถสำหรับผู้ใช้ในการเปลี่ยนภาษาได้ทันที? นั่นจะเป็นกรณีการใช้งานที่แตกต่างกัน
Antoine Jaussoin

แอพจะถูกรวบรวมสำหรับทุกภาษาที่ต้องการ เว็บเซิร์ฟเวอร์จะให้บริการเวอร์ชันที่ถูกต้องโดยอัตโนมัติขึ้นอยู่กับส่วนหัว "ยอมรับภาษา" หรือคุกกี้ที่กำหนดโดยผู้ใช้ทันที เมื่อทำเช่นนี้ตรรกะการแปลทั้งหมดอาจถูกย้ายออกจากแอป
Igor Sukharev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.