在 Node.js 專管語系檔案時,通常會使用物件格式來儲存字串。要在字串中嵌入變數,最常見的做法是使用大括號作為佔位符,並配合取代函數或是 i18next 這類套件來處理。
基礎實作方式
如果不使用外部套件,可以定義一個簡單的取代函式。在 translations.ts 中,字串會長這樣:
TypeScript
export const translations = {
welcome: "你好,{name}!歡迎回來。",
items: "你有 {count} 個待辦事項。"
};
接著撰寫一個處理邏輯,將大括號內的標記替換為實際數值:
TypeScript
function t(key: keyof typeof translations, variables: Record<string, string | number>) {
let message = translations[key];
Object.keys(variables).forEach(placeholder => {
const value = variables[placeholder];
message = message.replace(`{${placeholder}}`, String(value));
});
return message;
}
const greeting = t("welcome", { name: "小明" });
console.log(greeting); // 輸出:你好,小明!歡迎回來。
使用 i18next 套件
如果你使用的是業界標準的 i18next,語法會稍微不同,它預設支援雙大括號或是特定格式。在語系檔中:
TypeScript
{
"welcome": "你好,{{name}}!"
}
在程式碼中調用的方式:
TypeScript
import i18next from 'i18next';
i18next.t('welcome', { name: '小明' });
這種方式的好處是套件會自動幫你處理複雜的格式,例如數字千分位或日期格式化。
注意事項
確保變數名稱在字串與程式碼中完全一致。如果翻譯檔是從外部 JSON 匯入的,建議建立一個介面來約束變數名稱,避免因為打錯字導致變數沒有成功被取代,畫面直接顯示出 {name} 本身。
支援多層級物件的翻譯解析函式
這是一個支援多層級物件(例如:common.welcome)且能自動取代變數的進階實作。這種結構在大型 Node.js 專案中非常實用。
translations.ts 語系檔結構
建議將語系內容定義為巢狀物件,並使用 TypeScript 的物件結構來確保型別安全:
TypeScript
export const translations = {
common: {
welcome: "你好,{name}!歡迎來到我們的平台。",
error: "錯誤碼:{code}"
},
shop: {
cart: "你的購物車中有 {count} 件商品。"
}
};
解析函式實作
這個函式會根據點分隔符號(例如 “common.welcome”)找到正確的字串,再執行變數替換:
TypeScript
type TranslationData = typeof translations;
function getDeepValue(obj: any, path: string) {
return path.split('.').reduce((prev, curr) => prev && prev[curr], obj);
}
function translate(key: string, params: Record<string, string | number> = {}) {
let template = getDeepValue(translations, key);
if (!template || typeof template !== 'string') {
return key; // 如果找不到對應字串,回傳路徑名稱作為提示
}
// 遍歷所有傳入的變數並進行全域替換
Object.keys(params).forEach(param => {
const value = params[param];
const regex = new RegExp(`{${param}}`, 'g');
template = template.replace(regex, String(value));
});
return template;
}
如何使用
你可以在專案中的任何地方呼叫這個函式。這段邏輯模擬了 i18n 工具的基本行為:
TypeScript
const userName = "王小明";
const loginMessage = translate("common.welcome", { name: userName });
console.log(loginMessage); // 輸出:你好,王小明!歡迎來到我們的平台。
const cartMessage = translate("shop.cart", { count: 5 });
console.log(cartMessage); // 輸出:你的購物車中有 5 件商品。
進階建議
如果你的翻譯需求變得更複雜,例如需要處理複數形式(1 item vs 2 items)或性別差異,建議轉向使用 i18next 或 formatjs 這些成熟的函式庫。但對於大多數中小型專案,上述的字串取代邏輯已經足夠應付 90% 的情境。
要在 React 元件中處理這類動態變數,最直覺且不需要改動太多現有程式碼的方法是將 t.hi 視為範本字串,並在渲染時使用 replace 方法。
直接在 JSX 中處理
你可以直接在 <h2> 標籤內對字串進行取代。這種做法最快,適合專案規模較小的情況:
TypeScript
<h2 className="text-2xl font-semibold text-slate-900 mb-1">
{t.hi.replace('{username}', username)}
</h2>
建立一個簡單的翻譯工具函式
如果你的專案中有很多類似的變數需求,建議寫一個輔助函式處理取代邏輯,避免在 UI 程式碼中到處寫 replace:
TypeScript
// 輔助函式定義
const interpolate = (str: string, params: Record<string, string>) => {
let result = str;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`{${key}}`, value);
}
return result;
};
// 在元件中使用
<h2 className="text-2xl font-semibold text-slate-900 mb-1">
{interpolate(t.hi, { username })}
</h2>
將翻譯內容改為函式(推薦做法)
為了確保型別安全並減少字串處理的效能損耗,另一種常見的做法是讓語系檔中的特定項目支援函式:
TypeScript
// 修改語系檔定義
export const translations = {
zh: {
// ... 其他內容
hi: (username: string) => `Hi ${username}!`,
}
};
// 在元件中使用
<h2 className="text-2xl font-semibold text-slate-900 mb-1">
{typeof t.hi === 'function' ? t.hi(username) : t.hi}
</h2>
總結建議
- 如果只有一兩個地方要用,直接用 replace 即可。
- 如果你的校務建言系統規模會持續擴大,建議建立一個 interpolate 工具函式,這樣以後遇到
{date}或{count}等變數也能統一處理。 - 檢查你的
t.welcome是否也包含變數,處理邏輯是一樣的。