Monday, July 24, 2023

Hello/สวัสดี i18n สร้างเว็บรองรับหลายภาษาบน React

 สวัสดีครับผมแมกซ์นะครับ อากาศเย็นซะงั้นหรือผมรู้สึกไปเอง เหมือนจะไม่สบาย วันนี้มานำเสนอเครื่องมือช่วยในการสร้างและจัดการภาษาภายในเว็บ ซึ่งเป็นสิ่งสำคัญอย่างหนึ่ง ในตอนแรกผมสร้างเว็บแอพ โดยทั้งหมดใช้ภาษาอังกฤษ และมาลองคิดทบทวน ถามผู้ใช้อีกที สิ่งที่เค้าต้องการคือภาษาไทยสิ !เข้าใจง่าย ตรงไปตรงมา เราทำให้ผู้ใช้คือคนไทย งั้นก็เปลี่ยนทำเป็นไทยเลยไหม แต่สิ่งที่อยากได้ด้วยคือความสากล จึงเป็นที่มาของการใช้แนวคิด i18n เว็บรองรับหลายภาษา

i18n ??? หลายคนอาจจะผ่านตามาบ้าง i18n เนี่ยมันมาจาก Internationalization ยังไงหน่ะหรอก็เริ่มด้วย i จบด้วย n ตรงกลางมี 18 ตัวอักษรเลยกลายเป็น i18n ยังมี l10n มาจาก Localization หรือ k8s มาจาก Kubernetes แบบนี้ก็ได้หรอ !

เอา Console ลองนับ เออหว่ะ 555+ เพื่อ เดี๋ยวมือคนไปลองนับจะเหนื่อยเปล่า

react-i18next คือ internationalization addon ที่ใช้งานได้กับ React โดยในการเขียนโค้ดจะต่างจากเดิมนิดเดียวตัวอย่าง

<div>Just simple content</div>

หลังจากใช้ react-i18next โค้ดจะเป็นลักษณะนี้

<div>{t('simpleContent')}</div>

คือเปลี่ยนจากใช้ค่าตรงๆ เป็นการเรียกฟังก์ชั่นแล้วโยนพารามิเตอร์เป็น Key ของคำนั่นไปแทน เพื่อแสดงผลตามภาษาที่เราเลือกขณะนั้น ซึ่งสิ่งที่เราต้องเตรียมการดังนี้

เริ่มด้วยติดตั้ง react-i18next ไปที่โปรเจคของเพื่อนๆก่อน

# npm
$ npm install react-i18next --save

ไฟล์ที่ต้องสร้างเพิ่มเติม หรือ แก้ไข

my-app
├── locales
│ ├── en
│ └── translations.json
│ ├── th
│ └── translations.json
└── src
└── i18n.js
└── index.js
  • ไฟล์ translations.json ใน locales
{
"title": "Welcome to react using react-i18next",
"description": {
"part1": "This is part 1.",
"part2": "This is part 2."
}
}

ไฟล์ json ที่เก็บ key และ value ไว้ทั้งสองไฟล์ภาษามี key เหมือนกัน ต่างที่ value

{
"title": "ยินดีต้อนรับเข้าสู่การใช้ react-i18next บน react ",
"description": {
"part1": "นี่คือส่วนที่ 1",
"part2": "นี่คือส่วนที่ 2"
}
}
  • ไฟล์ i18n.js ใน src ตัว instance ไว้กำหนดตั้งค่า
import i18n from 'i18next';
import Backend from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { reactI18nextModule } from 'react-i18next';
i18n
.use(Backend)
.use(LanguageDetector)
.use(reactI18nextModule)
.init({
fallbackLng: 'en',
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
debug: true, interpolation: {
escapeValue: false, // not needed for react!!
},
react: {
wait: true
}
});
export default i18n;
  • ไฟล์ index.js ใน src
import './i18n';

เพิ่ม import instance เมื่อสักครู่เข้าไปด้วยเพียงเท่านั้น

  • ไฟล์ Component ของเราทุกไฟล์ถ้าต้องการให้รองรับการแสดงผลหลายภาษาต้องทำดังนี้

Import ตัว translate ไว้ใช้ในการ Export class นี้

import { translate } from "react-i18next";

เปลี่ยนการ Export ตัวเก่าเป็นผ่านตัว translate

// short for only loading one namespace:export default translate("translations")(Login);

ใน render() ทำการแกะของจาก props และสร้างตัวแปรฟังก์ชั่นสลับภาษาไว้ใช้

render() {
const { t, i18n } = this.props;

const switchingLanguage = () => {
if (i18n.language === "en") {
i18n.changeLanguage("th");
} else {
i18n.changeLanguage("en");
}
};
return(
<div>
//...
</div>
);
}

เมื่อทุกอย่างพร้อมแล้ว Component นี้ก็จะสามารถรองรับหลายภาษาได้แล้วโดยเรียก t ฟังก์ชั่นโยน key เป็น title เข้าไป

<div> {t('title')} </div>

การสลับภาษาสามารถเรียกใช้ switchingLanguage() ที่เราสร้างไว้ได้เลย ตัวอย่างมี ตัวหนังสือ EN/TH ไว้เมื่อกดภาษาก็จะสลับภาษาไปมา

<a onClick={() => switchingLanguage("en")}>{t("label.enth")}</a>

ก็จะได้ของหน้าตาแบบนี้ เว็บสองภาษาคลูๆไป

จบอ่าา หวังเพื่อนๆจะอย่าง งง 55 ถ้าอ่านและทำตามเอาเป็นว่าได้เป็นไอเดีย ให้คนที่แวะเข้ามาดูว่ามันทำได้แบบนี้ละกันนะครับ ไม่ได้ลงรายละเอียดเพราะมันมีเยอะกว่านี้อีกนะเออ ทำได้มากกว่านี้ บั๊บบายไปหล่ะครับ ก่อนจากมีลิ้งให้ไปต่อถ้าสนใจ

Source : https://medium.com/@themaxaboy/hello-%E0%B8%AA%E0%B8%A7%E0%B8%B1%E0%B8%AA%E0%B8%94%E0%B8%B5-i18n-%E0%B8%AA%E0%B8%A3%E0%B9%89%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%A7%E0%B9%87%E0%B8%9A%E0%B8%A3%E0%B8%AD%E0%B8%87%E0%B8%A3%E0%B8%B1%E0%B8%9A%E0%B8%AB%E0%B8%A5%E0%B8%B2%E0%B8%A2%E0%B8%A0%E0%B8%B2%E0%B8%A9%E0%B8%B2%E0%B8%9A%E0%B8%99-react-a491cfb7fc16


Tuesday, July 18, 2023

มาทำความรู้จัก React Context API กันเถอะ

 เมื่อโปรเจคของเราใหญ่มากขึ้นเรื่อยๆ เรามักจะเจอกับปัญหาการจัดการState วันนี้ผมจะมาพาทุกคนไปรู้จักกับReact APIตัวหนึ่งที่ช่วยให้เราสามารถจัดการStateได้สะดวกมากยิ่งขึ้น โดยเฉพาะในโปรเจคขนาดใหญ่

What is Context?

ContextคือReact APIที่ช่วยให้เราสามารถแชร์ข้อมูลระหว่างComponentsได้โดยไม่ต้องส่งProps

การแชร์ข้อมูลระหว่าง Components โดยไม่ใช้ Context

เมื่อก่อนสมัยที่เรายังไม่มีContextหากเราต้องการแชร์ข้อมูลกันระหว่างComponentsเราต้องประกาศStateไว้ที่Top-Level Componentและส่งเป็นPropsลงมาท่ีChildren Componentsเพื่อให้แต่ละComponentสามารถใช้ข้อมูลร่วมกันได้

Why?

แผนภาพ Components ในรูปแบบ DOM Tree

สมมติว่าจุดแต่ละจุดในแผนภาพด้านบนเป็นตัวแทนของComponentต่างๆ และให้ComponentsสีดำแทนComponentsที่มีการใช้ข้อมูลร่วมกัน ในทางตรงกันข้ามComponentsสีขาวจะไม่มีการใช้ข้อมูลร่วมกันเลย

เนื่องจากLeft ComponentและGrand-child of Right Componentต้องการใช้ข้อมูลร่วมกัน เราจึงต้องประกาศStateที่จะแชร์ข้อมูลไว้ที่Top-Level Componentและส่งเป็นPropsไปให้Left ComponentและGrand-child of Right Componentแต่เนื่องจากComponentนี้ไม่ได้อยู่ติดกับTop-Level Componentมันจึงจำเป็นต้องส่งPropsไปให้Right ComponentและChild of Right Componentก่อน แล้วจึงส่งต่อให้Grand-child of Right Component

คำถามคือทำไมComponentsที่ไม่ได้ใช้ข้อมูลร่วมกับLeft ComponentและGrand-child of Right componentต้องมาทำหน้าที่ส่งต่อPropsให้ด้วย วิธีนี้ทำให้โค้ดของเราดูไม่สบายตาเท่าไหร่นัก และนอกจากนี้หากPropsที่ส่งมามีการเปลี่ยนแปลง Componentsที่ทำหน้าที่ส่งข้อมูลให้ก็จะต้องRe-renderตามไปด้วย โดยที่ไม่จำเป็นเลย

การRe-renderนั้นควรเกิดขึ้นในComponentที่ใช้ข้อมูลนั้นๆเท่านั้น ไม่เช่นนั้นในที่สุดแล้วอาจทำให้เกิดปัญหาด้าน Performance ได้

และนี่จึงเป็นคำตอบของคำถามที่ว่าเรามีContextไว้ทำไม Contextเกิดขึ้นมาเพื่อลบข้อเสียที่ได้กล่าวมาข้างต้น

Contextช่วยให้เราสามารถใช้ข้อมูลร่วมกันระหว่างComponentsได้ โดยที่ไม่ต้องใช้Propsหากเราต้องการใช้ข้อมูลที่ใด ก็เรียกใช้Contextที่Componentนั้นพอ Componentsอื่นๆ และหากข้อมูลภายในContextเปลี่ยนแปลง ก็จะเกิดการRe-renderเฉพาะComponentsที่ใช้Contextนั้นเท่านั้น

นอกจากนี้ Context ยังช่วยทำให้ComponentsของเราสามารถReuseได้ง่ายขึ้นอีกด้วย เนื่องจากมันไม่ได้ผูกกับPropsแล้ว

When?

Context นั้นควรใช้ในกรณีที่จำเป็น เนื่องจากเป็นการเพิ่มความซับซ้อนให้แก่โค้ดของเรา ทำให้เราเจอความลำบากมากขึ้นเมื่อต้องมาแก้ไขโค้ดในภายหลัง

React DOM Treeแสดงเหตุการณ์ที่เหมาะสมต่อการใช้งานContext

จากDOM Treeด้านบนจะเห็นได้ว่าComponent1,6,7,8ต่างต้องการใช้งานข้อมูลร่วมกัน และแต่ละComponentนั้นอยู่ในระดับที่แตกต่างกันทั้งแนวตั้งและแนวดิ่ง การส่งเป็นPropsลงมาจะทำให้ทุกComponentต้องถือPropsเดียวกัน ทั้งที่บางComponentอาจไม่จำเป็นต้องใช้เลย ดังนั้นจึงควรใช้Contextเพื่อหลีกเลี่ยงปัญหาดังกล่าว

React DOM Treeแสดงเหตุการณ์ที่ไม่จำเป็นต้องใช้Context

ตัวอย่างกรณีที่ไม่จำเป็นต้องใช้Context เช่นเรามี3Componentsที่ต้องใช้ข้อมูลร่วมกัน แต่ว่าแต่ละComponentอยู่ห่างกันไม่เกิน1Level เคสแบบนี้การประกาศStateที่Component 1และส่งPropsมาที่Component2,4อาจเหมาะสมกว่า เนื่องจากจะเห็นได้ว่าไม่มีComponentไหนที่ได้รับข้อมูลโดยที่ไม่จำเป็นเลย

How?

เมื่อเรารู้จักContextและรู้ว่าควรใช้งานมันเมื่อไหร่แล้ว ก็ถึงเวลาเรียนรู้ว่าจะใช้งานมันได้อย่างไร

ก่อนที่ผมจะแนะนำวิธีการใช้งานContext ต้องขออนุญาตแนะนำContext APIที่จำเป็นต้องรู้จักกันก่อน

React.createContext

APIตัวแรกที่ต้องรู้จักคือcreateContext มันเป็นตัวสร้างContext Objectขึ้นมาเพื่อเก็บข้อมูลที่ต้องการใช้ร่วมกัน, ใช้เป็นProvider, และใช้Subscribe Contextที่Componentsปลายทาง

Context.Provider

อย่างที่เรียนให้ทราบด้านบน การสร้างContext Objectจะมาพร้อมกับProvider ซึ่งเจ้าProviderจะช่วยให้Componentsปลายทางทั้งหลายสามารถSubscribeการเปลี่ยนแปลงข้อมูลภายในContextได้

Class.contextType

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentsปลายทาง แต่จะใช้ได้กับClass Componentเท่านั้น และหากSubscribe Contextด้วยวิธีนี้เพียงวิธีเดียวจะทำให้เราสามารถSubscribeได้เพียงหนึ่งContextเท่านั้น

Context.Consumer

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentปลายทาง โดยที่สามารถใช้ได้กับทั้งClassและFunctional Component การSubscribe Contextด้วยวิธีนี้จะทำให้เราสามารถSubscribeได้มากกว่าหนึ่งContext

React.useContext

ใช้เพื่อSubscribeข้อมูลภายในContextที่Componentปลายทาง ใช้ได้กับFunctional Componentเท่านั้น การSubscribe Contextด้วยวิธีนี้จะทำให้เราสามารถSubscribeได้มากกว่าหนึ่งContext

วิธีนี้เป็นวิธีที่ง่ายและนิยมที่สุดในการSubscribeข้อมูลจากContext

Example

เรารู้จักContextกันมามากพอแล้ว สิ่งต่อไปที่เราอยากเห็นคงหนีไม่พ้นตัวอย่างการใช้งาน

ต้องขอบอกว่าวิธีการใช้งานที่ผมกำลังจะนำเสนอเป็นหนึ่งในวิธีการใช้งานเท่านั้นและไม่การันตีว่าจะเป็นวิธีการที่ดีที่สุด

ภาพตัวอย่างการสร้างProvider

ในที่นี้เราจะสร้างCart Contextเพื่อใช้เก็บข้อมูลสินค้าในรถเข็น

เริ่มแรกเราต้องสร้างContext Objectขึ้นมาก่อน แล้วจึงสร้างCartProvider Componentเพื่อใช้เป็นที่เก็บStateและActionต่างๆของContext

ในCartProviderเราจะสร้างStateขึ้นมาเพื่อใช้เป็นที่เก็บข้อมูลต่างๆที่ต้องการใช้ร่วมกัน และสร้างAction Fucntionต่างๆที่เราจะอนุญาตให้Componentปลายทางสามารถทำได้เช่น addToCart

ต่อมาเราจะสร้างตัวแปรcartStoreขึ้นมาเพื่อเป็นValueให้แก่PropของProvider ซึ่งPropตัวนี้จะเป็นค่าที่เราจะส่งไปให้Consumerนั่นเอง

ซึ่งค่าของตัวแปรcartStoreผมดีไซน์แยกออกเป็นสองส่วนคือส่วนของStateและAction จากโค้ดจะเห็นได้ว่าStateจะอยู่ในLevelของroot ส่วนActionจะถูกwrapไว้ด้วยkey cartAction อีกที

วิธีนี้ทำให้เมื่อเราอ่านโค้ดที่Componentปลายทาง เราสามารถเข้าใจได้ในทันทีเลยว่ามันกำลังใช้ค่าจากStateหรือกำลังจะทำActionอะไรบางอย่างกับContextอยู่

สุดท้ายเราจะสร้างCustom Hookชื่อว่าuseCartContextขึ้นมา ประโยชน์ของHookตัวนี้คือทำให้เราไม่ต้องImport useContextและCartContextในทุกComponentปลายทาง ที่ต้องการSubscribe เพียงImport useCartContextไปตัวเดียวก็สามารถใช้ข้อมูลของContextได้แล้ว ทำให้โค้ดคลีนขึ้น

เมื่อสร้างCartProviderเสร็จแล้ว เราก็จะนำProvider ตัวนี้ไปครอบเป็นParent Component ที่ระดับApp levelเพื่อให้ทุกComponentในแอปของเราสามารถเรียกใช้งานContextตัวนี้ได้นั่นเอง

โค้ดตัวอย่างการใช้งานProvider

หากเรามีมากกว่าหนึ่งContextเราก็สามารถสร้างProviderอื่นๆขึ้นมาได้และนำมาใช้ในรูปแบบเดียวกัน

โค้ดตัวอย่างการSubscribe Contextที่Componentปลายทาง

หากComponentไหนต้องการใช้งานCartContextเพียงแค่Import useCartContextมาใช้ ก็จะสามารถใช้งานได้โดยที่ข้อมูลที่ได้รับจะเป็นข้อมูลที่แชร์กันระหว่างComponents

High Order Componentเพื่อใช้Subscribe Context

หากเราต้องการSubscribe Context Valueที่Class Componentเราอาจสร้างHigh Order Componentขึ้นมาตัวหนึ่งเพื่อใช้ส่งContext Valueไปเป็นPropให้กับComponentปลายทาง

Caution!!!

จุดที่ทุกคนควรรู้และระวังคือContextไม่มีการพิจารณาว่าComponentsปลายทางสมควรที่จะถูกRe-renderหรือไม่ เพราะฉะนั้นทุกครั้งที่ข้อมูลภายในContextเปลี่ยนแปลง Componentปลายทางจะถูกสั่งRe-renderด้วย

หากเราต้องการให้โค้ดบางส่วนทำงานก็ต่อเมื่อข้อมูลในContextบางตัวเปลี่ยนเท่านั้น ไม่ใช่ทุกตัว เราอาจใช้useMemoเข้ามาช่วยได้ครับ

หากสนใจศึกษาข้อมูลเกี่ยวกับContext APIเพิ่มเติม สามารถดูได้จากOfficial Documentเลยครับ

Source : https://medium.com/@pakpoom.thawee/%E0%B8%A1%E0%B8%B2%E0%B8%97%E0%B8%B3%E0%B8%84%E0%B8%A7%E0%B8%B2%E0%B8%A1%E0%B8%A3%E0%B8%B9%E0%B9%89%E0%B8%88%E0%B8%B1%E0%B8%81-react-context-api-%E0%B8%81%E0%B8%B1%E0%B8%99%E0%B9%80%E0%B8%96%E0%B8%AD%E0%B8%B0-511d1bd332f1