撰寫語言包載入文件 === ###### With Next.js App Router + next-intl :::info :radio_button: [回到目錄攻略](https://hackmd.io/@hJJ8etrATgudRfKA1gryew/BJiplPwMC) ::: 1.建立google-sheet --- <font color="#F7A004">**建立一個新的google試算表,初步寫好語言的分類表格,key, zh, en各一行,如下圖:**</font> ![image](https://hackmd.io/_uploads/Hk5Iu3FMR.png) <font color="#F7A004">**可再以全域(global)以及個別頁面做為區分,建立sheet的分頁:**</font> ![image](https://hackmd.io/_uploads/HJlut3tMR.png) <font color="#F7A004">**接著按下 檔案 > 公用 > 發布到網路**</font> ![image](https://hackmd.io/_uploads/r1P1hstfA.png) <font color="#F7A004">**發布成功後,在成功畫面的有個「連結」,將下面釋出的選項選擇選成「逗號分隔值」,並複製好該連結,等等要作為`SOURCE_CSV_URL`的參數**</font> ![image](https://hackmd.io/_uploads/HkgjnoYfA.png) 2.載入相關套件 --- 分別在TERMINAL打載入指令:`npm i ${module}` module分別為 - `fs` : Node.js 中的檔案系統模組,用於處理文件操作,包括讀寫、刪除、創建文件等。 - `csv-parse`:用於解析CSV 格式的資料。 - `axios`:用於發送HTTP 請求。 3.撰寫`lang-download.js` --- 這就是本機執行工具,只要在TERMINAL輸入神奇指令(第四步會在細說),取得遠端 google-spreadsheet 文件的CSV,再轉成json格式儲存,就能載入語言包囉。 > @/lang-download.js 首先,引入必要module -- ```javascript= const fs = require("fs").promises; // 使用 fs.promises 可以使 fs 操作變為 Promise-based const { parse } = require("csv-parse"); const axios = require("axios"); ``` 定義一些常數和變數 -- ```javascript=4 // 提供專案中語言包的路徑位置 const LANG_PATHS = { zh: "./messages/zh/", en: "./messages/en/", }; // 將剛剛google-sheet發布網路的csv連結存成參數 const SOURCE_CSV_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vRzzJn9Lb9s8SUZ4mweZcsXZ6rpfCk355i_fFgxlZrYP9jgtsiiay6K--LwY6zZMGPmvc6zZMQv8Ts-/pub?output=csv"; // 建立為增加檔案中的`TAB_ARRAY`陣列。 // `gid`為`google-spreadsheet`中,每個分頁網址最後git=的數字; // `fileName`為`${分頁名稱} + ".json"` const TAB_ARRAY = [ { gid: "0", fileName: "1_global.json" }, { gid: "59220473", fileName: "main-home.json" }, ]; ``` 定義非同步函數`fetchCsvFromUrl`。此函數使用axios傳送HTTP GET 請求來取得指定URL 的CSV 資料。使用csv-parse解析CSV 資料並傳回一個Promise 物件。 -- ```javascript=21 async function fetchCsvFromUrl(url) { const res = await axios.get(url); return new Promise((resolve, reject) => { parse(res.data, { columns: true }, (err, output) => err ? reject(err) : resolve(output) ); }); } ``` 定義非同步函數`getLangs`。該函數根據給定的`gidVal`從`Google Spreadsheet`檔案中取得資料。將獲取的資料轉換為包含不同語言翻譯的對象,並返回 -- ```javascript=29 async function getLangs(gidVal, languages) { if (!gidVal) { throw new Error("param of gidVal on getLangs is missing !"); } try { const langList = await fetchCsvFromUrl(`${SOURCE_CSV_URL}&gid=${gidVal}`); const translations = {}; languages.forEach((lang) => { translations[lang] = {}; }); langList.forEach((item) => { if (!item.key) return; languages.forEach((lang) => { translations[lang][item.key.trim()] = item[lang]?.trim() || ""; }); }); return translations; } catch (err) { console.error(err.message); throw err; } } ``` 定義非同步函數`writeJsonFile`。此函數用於將給定的資料以`JSON`格式寫入指定路徑的檔案中。 ```javascript=56 async function writeJsonFile(path, data) { try { await fs.access(path); console.log(`${path} 存在`); } catch (err) { console.log(`${path} 不存在,已幫您新增此路徑檔案`); } const jsonData = JSON.stringify(data, null, 2); try { await fs.writeFile(path, jsonData); console.log(`${path} 文件寫入成功 `); } catch (err) { console.error(`${path} 文件寫入失敗 `); console.error(err.message); } } ``` 定義非同步函數`generateI18nFile`。此函數根據給定的工作表數組,產生用於國際化的`TypeScript` 程式碼,並將其寫入到指定路徑的檔案中。 ```javascript=73 async function generateI18nFile(TAB_ARRAY) { try { let code = ` import { getRequestConfig } from "next-intl/server"; import { notFound } from "next/navigation"; import { locales } from "./config"; export default getRequestConfig(async ({ locale }) => { if (!locales.includes(locale as any)) notFound() const messages = {`; for (const item of TAB_ARRAY) { code += `...(await import(\`@/messages/\${locale}/${item.fileName}\`)).default,`; } code += ` } return { messages, } }) `; await fs.writeFile("./src/lib/i18n.ts", code); console.log("./src/lib/i18n.ts" + "文件寫入成功"); } catch (err) { console.error("./src/lib/i18n.ts" + "文件寫入失敗"); console.error(err.message); } } ``` 定義非同步函數`main()` -- - 此函數是腳本的主要邏輯,呼叫其他函數以執行整個過程。 - 遍歷`TAB_ARRAY`數組,對每個工作表執行以下操作:呼叫`getLangs()`函數取得翻譯資料。將翻譯資料寫入對應的`JSON`檔案中,輸出一些提示訊息。 ```javascript=105 async function main() { const languages = Object.keys(LANG_PATHS); await generateI18nFile(TAB_ARRAY); for (const item of TAB_ARRAY) { try { const translations = await getLangs(item.gid, languages); if (languages.some((lang) => !Object.keys(translations[lang]).length)) { console.warn( ` ${item.gid}, ${item.fileName} 語系可能全都是空白的語言包, 已中止運行` ); console.log("---"); return; } languages.forEach((lang) => { writeJsonFile( `${LANG_PATHS[lang]}${item.fileName}`, translations[lang] ); }); console.log("---"); } catch (err) { console.error(err.message); } } } main(); ``` 4.撰寫語言包下載指令 --- > @/package.json ```json= { ... "scripts": { "fetch-lang": "node lang-download.js" } } ``` 這樣即表示,在TERMINAL執行script,可輸入: ``` # bash npm run fetch-lang # OR node lang-download.js ``` 就開始自動載入語言包了!相關的載入狀況/行為大概是: - 將所有語言包路徑寫入`@src/lib/i18n.ts`檔案裡 - 確認/回報所有語言包是否存在 - 寫入該語言`json`檔,並確認/回報是否寫入成功 像是這樣就完成寫入啦: ![image](https://hackmd.io/_uploads/rJigok9zC.png)