วิธีการทำ React 2 ภาษาด้วย React i18n-next

Published on
React
2018/04/getting-started-with-react-i18next
Discord

สวัสดีครับ วันนี้ผมมีตัวอย่างการทำเว็บ 2 ภาษา ด้วย React กันครับ เผื่อว่าบางคนเวลาทำเว็บด้วย React แล้วเวลาใช้งานฟังค์ชัน 2 ภาษา อาจจะต้องส่ง key lang แนบไปที่ header หรือว่าทุกๆ request แล้วให้ทางฝั่ง Server นั้นคอย handle แต่ว่าบทความนี้ไม่ใช่แบบนั้น แต่จะเป็นการเปลี่ยนภาษาทางฝั่ง Client ไปเลย ด้วยพระเอกของงานนี้ นั่นคือ React i18next นั่นเอง ตัวอย่างการทำเว็บ 2 ภาษาด้วย React i18n Next

Step 1 : Create Project

เริ่มต้นสร้างโปรเจ็คขึ้นมาใหม่เลย ซึ่งในตัวอย่างนี้ผมจะใช้ Create React App ในการสร้างเพราะมันง่าย ไม่ต้อง Setup อะไรให้มันยุ่งยากเท่าไหร่

npx creact-react-app learn-react-i18n
cd learn-react-i18n

npm start

สำหรับใครไม่รู้จัก หรือไม่เคยสร้างโปรเจ็คด้วย Create React App เชิญอ่านเพิ่มเติม Create React App

จะต้องได้หน้าเว็บแบบนี้ แสดงว่าเรามาถูกทางแล้ว

Create React App Default

Step 2 : Setup i18n

ต่อมาทำการติดตั้ง Dependencies ต่างๆที่ใช้กับ React i18next กัน

npm install i18next react-i18next i18next-xhr-backend
  • i18next : ตัว Library หลัก
  • react-i18next : ตัว Library ที่แปลงมาเป็นเวอร์ชันที่ใช้สำหรับ React
  • i18next-xhr-backend : เป็นคล้ายๆตัว Controller ที่มันจะโหลดไฟล์ locale JSON ให้เราและทำการ parse ข้อมูล

ต่อมาทำการสร้างไฟล์ i18n.js ขึ้นมา (ที่เดียวกันกับ App.js)

import i18n from 'i18next'
import XHR from 'i18next-xhr-backend'
import { reactI18nextModule } from 'react-i18next'

i18n
  .use(XHR)
  .use(reactI18nextModule)
  .init({
    fallbackLng: 'th',
    ns: ['trans'],
    defaultNS: 'trans',
    debug: true,
    interpolation: {
      escapeValue: false
    },
    react: {
      wait: true
    }
  })

export default i18n

และก็ขั้นตอนสุดท้ายในการ Setup ก็คือทำการเพิ่มไฟล์ locale ซึ่ง default มันคือ locales/lang/trans.json ซึ่งในบทความจะใช้ 2 ภาษาคือ ไทยและอังกฤษ ก็เลยสร้าง 2 ไฟล์คือ

  • /public/locales/en/trans.json : สำหรับข้อมูลภาษาอังกฤษ
  • /public/locales/th/trans.json : สำหรับข้อมูลภาษาไทย

ตัวอย่างไฟล์ en/trans.json

{
  "title": "React i18n",
  "subtitle": "React i18n with i18next Tutorial",
  "lang_text": "Please select language : ",
  "lang_en": "English",
  "lang_th": "Thai",
  "header": "Example of React i18next",
  "content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus nisi perferendis repellat consequatur commodi esse, delectus, cum in, ab illo maxime similique maiores culpa exercitationem ullam mollitia, ad ratione inventore?",
  "welcome_message": "Hello, {{name}}. Welcome to {{website}}!",
  "read_more": "Read more",
  "post": "Post :",
  "source_code": "Source Code :"
}

ตัวอย่างไฟล์ th/trans.json

{
  "title": "React i18n",
  "subtitle": "React i18n with i18next Tutorial",
  "lang_text": "กรุณาเลือกภาษา : ",
  "lang_en": "ภาษาอังกฤษ",
  "lang_th": "ภาษาไทย",
  "header": "ตัวอย่างการใช้งาน React i18next",
  "content": "Lorem Ipsum คือ เนื้อหาจำลองแบบเรียบๆ ที่ใช้กันในธุรกิจงานพิมพ์หรืองานเรียงพิมพ์ มันได้กลายมาเป็นเนื้อหาจำลองมาตรฐานของธุรกิจดังกล่าวมาตั้งแต่ศตวรรษที่ 16",
  "welcome_message": "สวัสดี, {{name}}. ยินดีต้อนรับสู่เว็บไซต์ {{website}}!",
  "read_more": "อ่านบทความต่อ",
  "post": "บทความ :",
  "source_code": "ซอร์สโค๊ด :"
}

Step 3 : with High Order Function (HOC)

การใช้งาน i18next แบบง่ายสุด ก็คือทำผ่าน (HOC) (High Order Function)

วิธีการก็คือ เมื่อเรายัด component ผ่าน HOC แล้ว เราก็จะสามารถเรียก function t ที่เป็น function ของ i18next ที่ให้เราสามารถเรียกใช้งานได้เลยผ่าน Props ตัวอย่าง เช่น

const { translate } from 'react-i18next'

const App = ({ t }) => (
  <div>
    <h1>{t('KEY_NAME')}</h1>
  </div>
)

export default translate()(App)

Step 4 : with Render Props

การใช้งาน i18next อีกแบบหนึ่งก็คือ การใช้งานผ่าน Render ผ่านตัว instance ของ i18next นั่นเอง

วิธีการก็คือเราต้องสร้าง children ภายใน <I18n> เป็นฟังค์ชัน เพื่อทำการ render ประมาณนี้

import { I18n } from 'react-i18next'

const App = () => (
  <I18n>
    <MyApp />
  </I18n>
)

const MyApp = ({ t }) => (
  <div>
    <h1>{t('KEY_NAME')}</h1>
  </div>
)

Step 5 : Interpolate

ต่อมาการใช้งาน Interpolate หรือก็คือ Dynamic Message ตัวอย่าง เช่น เรา อยากให้มัน translate คำ โดยเปลี่ยนแปลงค่าตามตัวแปรที่เราต้องการ เช่น

ภาษาอังกฤษ

Hello World {{currentUser}}

ภาษาไทย

สวัสดี {{currentUser}}

ซึ่ง มันแปลให้อยู่แล้ว ในคำทักทาย แต่ว่าตัว currentUser มันจะไม่แปลให้ และจะเปลี่ยนขึ้นอยู่กับว่า เราจะส่ง ตัวแปรอะไรไป และวิธีการเรียกใช้งานก็แค่ ส่ง Object พร้อม key เดียวกันกับไฟล์ locale ไป ใน argument ที่ 2 ดังเช่น

t('KEY_NAME', { currentUser: 'John Doe' })

ซึ่งผลลัพธ์ก็จะได้เป็น

Hello World John Doe
สวัสดี John Doe

แค่นี้เอง ง่ายๆ

ซึ่ง Default ตัว interpolate จะดู key ตาม format {{}} แต่เราสามารถ custom ได้

Step 6 : Implement App.js

ต่อมา หลังจากดูตัวอย่างไปหลายๆแบบละ คราวนี้จะมาลองทำการ Implement App จริงๆ ต่อจาก Step ที่ 2 หลังจากที่เราได้ไฟล์ locales ของเราแล้ว ถัดมาคือการเปลี่ยนไฟล์ App.js

ซึ่งผมทำการ Custom ขึ้นมาดังนี้

import React from 'react'
import i18n from './i18n'
import { translate } from 'react-i18next'

import './App.css'

const App = ({ t }) => (
  <div className="App">
    <section className="hero is-info">
      <div className="hero-body has-text-centered">
        <div className="container">
          <h1 className="title">{t('title')}</h1>
          <h2>{t('subtitle')}</h2>
        </div>
      </div>
    </section>

    <div className="language-button-section">
      <span>{t('lang_text')}</span>
      <button
        className="button"
        onClick={() => {
          i18n.changeLanguage('th')
        }}
      >
        {t('lang_th')}
      </button>
      <button
        className="button"
        onClick={() => {
          i18n.changeLanguage('en')
        }}
      >
        {t('lang_en')}
      </button>
    </div>

    <div className="container columns">
      <div className="column is-8 is-offset-4">
        <div className="box normal-content">
          <h3 className="title">{t('header')}</h3>
          <p>{t('content')}</p>
        </div>

        <div className="box interpolate-content">
          <p>
            {t('welcome_message', {
              name: 'Chai Phonbopit',
              website: 'devahoy.com'
            })}
          </p>
        </div>

        <div className="box info">
          <h3 className="title">{t('read_more')}</h3>
          <ul>
            <li>
              <strong>{t('post')}</strong>{' '}
              <a
                href="https://devahoy.com/blog/2018/04/getting-started-with-react-i18next/"
                target="_blank"
              >
                วิธีการทำ React 2 ภาษาด้วย React i18n-next
              </a>
            </li>
            <li>
              <strong>{t('source_code')}</strong>{' '}
              <a
                href="https://github.com/Devahoy/hello-react-i18next"
                target="_blank"
              >
                hello-react-i18next
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <footer className="footer">
      <div className="container">
        <div className="content has-text-centered">
          <p>
            Powered by{' '}
            <a href="https://devahoy.com" target="_blank">
              devahoy.com
            </a>
          </p>
        </div>
      </div>
    </footer>
  </div>
)

export default translate()(App)

และแก้ไฟล์ public/index.html ซะหน่อย เนื่องจากผมใช้ Bulma มาเป็น CSS Framework

<head>
  <link
    rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.2/css/bulma.min.css"
  />
  <link
    href="https://fonts.googleapis.com/css?family=Montserrat"
    rel="stylesheet"
  />
</head>

อธิบายเพิ่มเติมจาก App.js ซักนิด

  • t('KEY_NAME') : ทำการ translate ตาม key ที่มีในไฟล์ locale ของเรา
  • t('KEY_NAME', { key: value }) : ทำการ translate แบบ Dynamic โดยเปลี่ยนค่า template เป็น key ของเรา
  • i18n.changeLanguage() : คือ method ที่เราใช้สำหรับเปลี่ยนภาษา โดยรับ เป็น locale เช่น en, th หรือ de เป็นต้น
  • translate()(App) : เป็น HOC ที่ทำให้เราสามารถเรียกใช้ props ของ i18next ได้เลย

ซึ่งจะบอกว่าตัวอย่างบทความนี้ก็เป็นเพียงแค่ Basic Example เท่านั้น มันยังสามารถ Custom หรือเรียกใช้ได้หลายๆแบบ เช่น ใช้ไฟล์ template หลายๆไฟล์ ทำยังไง แต่ละ Component จะใช้ locale แยกยังไง? หุ้ม i18nProvider ไปเลยดีไหม หรือปรับเปลี่ยน template ในการทำ interpolation ก้ได้ เช่นจากใช้ {{}} อาจจะเปลี่ยนเป็น [] หรือ ภายใน %% อะไรก็ว่าไป

สุดท้าย Source Code ของบทความนี้ครับ

Reference

Buy Me A Coffee
Authors
Discord