---
# System prepended metadata

title: 撰寫語言包載入文件
tags: [Next.Js 14 with APP Router]

---

撰寫語言包載入文件
===

###### 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)

