撰寫語言包載入文件
===
###### 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>

<font color="#F7A004">**可再以全域(global)以及個別頁面做為區分,建立sheet的分頁:**</font>

<font color="#F7A004">**接著按下 檔案 > 公用 > 發布到網路**</font>

<font color="#F7A004">**發布成功後,在成功畫面的有個「連結」,將下面釋出的選項選擇選成「逗號分隔值」,並複製好該連結,等等要作為`SOURCE_CSV_URL`的參數**</font>

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`檔,並確認/回報是否寫入成功
像是這樣就完成寫入啦:
