iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
JavaScript

從PM到前端開發:我的React作品集之旅系列 第 25

Day 25: 利用單例模式優化 React 應用的 i18n 支援

  • 分享至 

  • xImage
  •  

在前一篇文章中,我們展示了如何將多語言資料從本地 JSON 檔案轉換為 API 形式,並將其部署到 Vercel 進行集中管理。這樣的改進使我們的應用能夠動態從伺服器獲取翻譯資料,提升了多語言管理的靈活性與效能。

今天,我們將繼續這個基礎,深入探討如何在前端 React 應用中整合這個 API,並實現翻譯資料的快取,避免重複請求,提升應用的效能。這將包括利用單例模式來管理 i18n 實例,並進行多語言切換的效能優化。

單例模式 (Singleton) 簡介

單例模式 是設計模式中的一種,它的主要特點是保證一個類在應用程序中只有一個實例,並提供一個全局的訪問點。具體來說,單例模式通常應用在需要共享全局狀態或設定的場景,確保狀態的一致性。

在這篇文章中,我們使用了單例模式來管理 i18n 的初始化邏輯。這樣可以確保 i18n 的初始化只會執行一次,即使在應用中的不同部分多次調用,也始終會返回同一個 i18n 實例,避免了重複初始化的問題。這不僅提高了效能,還能保持語言設定和翻譯資料的一致性。

單例模式的好處:

  1. 狀態一致性:應用中的每個部分都會共享同一個 i18n 實例,保證狀態和翻譯資料的一致性。
  2. 避免重複初始化:通過單例模式,i18n 的初始化過程只會執行一次,提升應用的效能。
  3. 易於維護:所有語言相關的操作都集中於同一個實例,方便維護和擴展。

在本實作中,當我們第一次創建 i18n 實例時,系統會檢查是否已經有一個實例存在,如果不存在則創建一個,否則直接返回現有的實例。這樣的設計模式讓我們的應用更加靈活且高效。

實際演練

接下來,我們來看如何使用 i18next-chained-backendi18next-localstorage-backend 來優化翻譯資料的加載。

Step 1: 安裝 i18next-chained-backend

首先,我們需要安裝兩個插件來支援我們的翻譯資料動態加載及快取:

npm install i18next-chained-backend i18next-localstorage-backend
  • i18next-chained-backend:這個插件允許我們將多個翻譯資料來源鏈接在一起。例如,我們可以首先嘗試從本地快取讀取資料,若快取不存在或過期,則回退到遠端的 API。
  • i18next-localstorage-backend:這個插件負責將翻譯資料儲存在 localStorage 中,讓應用下次訪問時可以直接使用快取資料,提升效能。

Step 2: 優化LangButton

我們將語言偏好儲存到 localStorage 中,以便後續使用。在語言切換過程中,可能會面臨網路延遲或 API 請求失敗的情況,因此需要一個可靠的錯誤處理機制來確保即時反饋用戶操作。

//src/components/navBar/Langbutton 

import React from 'react'
import i18n from '@/utils/i18n';
import * as styles from './LangButton.module.scss';
import { log } from '@/utils/log';
import loadChineseFont from '@/utils/loadChineseFont';

const LangButton = ({ }) => {
    // 切換語言選項的函數
    const toggleLanguage = async () => {
        const newLang = i18n.language === 'en' ? 'zh' : 'en';
        if (newLang === 'zh') {
            loadChineseFont();
        }
        try {
            await i18n.changeLanguage(newLang);
            localStorage.setItem('language', newLang);  // 保存語言偏好到 localStorage
        } catch (err) {
            log(logLevel.ERROR, `Language switch error: ${err}`);
        }
    }

    log(logLevel.DEBUG, 'LangButton rendered');

    return (
        <button className={styles.lang_btn}
            onClick={toggleLanguage}>
            🌐 {i18n.language.toUpperCase()} ▼
        </button>
    )
}

export default React.memo(LangButton);

說明:

  • 異步錯誤處理:通過 try-catch 來捕捉 i18n.changeLanguage 的潛在錯誤,確保在網路或翻譯載入失敗的情況下,應用不會崩潰。

Step 3: 偵測用戶語言並實作快取機制

在這一步,我們將確保應用能自動偵測用戶的語言偏好,並通過 localStorage 快取翻譯資料,避免每次重新請求 API。

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-chained-backend';
import LocalStorageBackend from 'i18next-localstorage-backend'; // primary use cache
import HttpApi from 'i18next-http-backend';
import config from '@/utils/config';
import { log } from '@/utils/log';

let instance = null;

const createI18nInstance = () => {
    if (!instance) {
        console.log("createI18nInstance");

        // 支援的語言
        const supportedLanguages = ['en', 'zh'];

        // 從 localStorage 或瀏覽器語言檢測得到用戶的語言
        let defaultLanguage = localStorage.getItem('language') || navigator.language.split('-')[0];

        // 檢查是否支援該語言,否則預設為英文
        if (!supportedLanguages.includes(defaultLanguage)) {
            defaultLanguage = 'en';
        }

        // 保存語言到 localStorage,以便下次訪問時使用
        localStorage.setItem('language', defaultLanguage);

        i18n
            .use(Backend)  // 使用 i18next-http-backend
            .use(initReactI18next)
            .init({
                lng: defaultLanguage, // 預設語言
                fallbackLng: "en", // 找不到語言時回退語言
                debug: config.featureFlags.enableLogging && config.logLevel === logLevel.DEBUG,  // 只有當 logLevel 設置為 DEBUG 時,啟用 debug 模式
                interpolation: {
                    escapeValue: false, // React 已經防止了 XSS 攻擊
                },
                useSuspense: false,
                backend: {
                    backends: [
                        LocalStorageBackend,  // primary backend
                        HttpApi               // fallback backend
                    ],
                    backendOptions: [{
                        /* options for primary backend */
                        enabled: true,  // 啟用快取
                        prefix: 'i18next_res_',  // prefix for stored languages
                        versions: { en: 'v1.0', zh: 'v1.0' },  // 版本控制快取
                        expirationTime: 7 * 24 * 60 * 60 * 1000,  // 7天後過期
                        // can be either window.localStorage or window.sessionStorage. Default: window.localStorage
                        store: typeof window !== 'undefined' ? window.localStorage : null
                    }, {
                        /* options for secondary backend */
                        loadPath: (languages, namespaces) => {
                            const path = `${process.env.REACT_APP_I18N_API_URL}?lang=${languages[0]}`;
                            log(logLevel.DEBUG, `Dynamically generated loadPath: ${path}`);  // 打印 loadPath
                            return path;
                        },
                        crossDomain: true,
                    }],
                },
            });
        instance = i18n;
    }
    return instance;

};

export default createI18nInstance;

說明

  • 偵測語言:應用會根據 localStorage 或瀏覽器的語言設定自動選擇適合的語言。
  • 快取機制:使用 i18next-localstorage-backend 來儲存翻譯資料,並設定 7 天的過期時間,提升效能並減少不必要的 API 請求。
  • 版本控制:透過版本控制來管理不同語言版本,當資料版本更新時,自動重新加載翻譯資料。

結語

透過結合 navigator.languagelocalStorage,我們為應用提供了一個智能且靈活的語言切換機制。這種方式能夠自動偵測用戶的語言需求,並讓用戶手動調整語言後能保存其選擇,進一步提升應用的使用體驗。此外,我們使用了快取機制來優化翻譯資料的加載效能,減少不必要的 API 請求。

最後,我們也介紹了 單例模式 如何幫助我們有效管理應用中的全域狀態,確保 i18n 實例的唯一性並提升應用的效能。如果你對更多前端技術或專案實作有興趣,請持續關注我們的後續文章!


流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!



上一篇
Day 24: 從 JSON 到 API,優化 i18n 資料加載
下一篇
Day 26: 如何在 React 中追蹤用戶行為
系列文
從PM到前端開發:我的React作品集之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言