傳統 .resx 資源檔痛點
- 新增控制項需開發人員手動加入資源鍵值。
- 文字修改需改 code、重新編譯、部署。
- 業務單位無法自行調整術語 (如「客戶」↔「顧客」)。
本方案核心架構
資料庫表 LanguageText
├── ControlId (PK) -- 控制項唯一識別 (頁面+ID)
├── Culture (PK) -- zh-TW, en-US, ja-JP...
├── DisplayText -- 顯示文字
└── TooltipText -- 提示文字 (可選)
執行流程:
1. 頁面載入 → 遞迴掃描所有目標控制項 (Label, Button, GridView Header...)
2. 依 ControlId + 當前 Culture 查詢 DB → 套用 Text/ToolTip
3. 管理頁面 → 列出所有可設定控制項 → 編輯多語文字 → 存回 DB
4. 下次造訪自動套用新文字,零部署
資料庫結構
CREATE TABLE dbo.PageText (
PagePath NVARCHAR(260) NOT NULL, -- ~/Admin/UserList.aspx
ControlId NVARCHAR(128) NOT NULL, -- lblTitle, btnSave, gvUsers_Header_Name
Culture CHAR(5) NOT NULL, -- zh-TW, en-US, ja-JP
DisplayText NVARCHAR(MAX) NULL,
TooltipText NVARCHAR(MAX) NULL,
UpdatedAt DATETIME2 DEFAULT SYSUTCDATETIME(),
UpdatedBy NVARCHAR(50),
CONSTRAINT PK_PageText PRIMARY KEY (PagePath, ControlId, Culture)
);
核心程式碼:基底頁面自動套用
public class LocalizedPage : System.Web.UI.Page
{
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!IsPostBack) ApplyLocalization();
}
private void ApplyLocalization()
{
string culture = Thread.CurrentThread.CurrentUICulture.Name; // "zh-TW"
string pagePath = Request.AppRelativeCurrentExecutionFilePath; // "~/Admin/UserList.aspx"
var texts = DbHelper.GetPageTexts(pagePath, culture); // Dictionary
ApplyToControls(this, texts);
}
private void ApplyToControls(Control parent, Dictionary texts)
{
foreach (Control c in parent.Controls)
{
string key = $"{pagePath}_{c.ID}";
if (texts.TryGetValue(key, out var pair))
{
SwitchText(c, pair.DisplayText, pair.TooltipText);
}
if (c.HasControls) ApplyToControls(c, texts);
}
}
private void SwitchText(Control c, string display, string tooltip)
{
switch (c)
{
case Label l: l.Text = display; break;
case Button b: b.Text = display; break;
case LinkButton lb: lb.Text = display; break;
case HyperLink hl: hl.Text = display; break;
case GridView gv: ApplyGridViewHeaders(gv, display); break; // 需對應 Header 控制項 ID
}
if (!string.IsNullOrEmpty(tooltip)) c.ToolTip = tooltip;
}
}
管理介面:掃描並編輯
<%@ Page Language="C#" Inherits="LocalizedPage" %>
<asp:Button ID="btnScan" Text="掃描本頁可本地化控制項" OnClick="btnScan_Click" />
<asp:GridView ID="gvTexts" AutoGenerateColumns="False" runat="server">
<Columns>
<asp:BoundField DataField="ControlId" HeaderText="控制項" />
<asp:TemplateField HeaderText="繁體中文">
<ItemTemplate><asp:TextBox ID="txtZh" Text='<%# Eval("ZhText") %>' runat="server" /></ItemTemplate>
</TemplateField>
<asp:TemplateField HeaderText="English">
<ItemTemplate><asp:TextBox ID="txtEn" Text='<%# Eval("EnText") %>' runat="server" /></ItemTemplate>
</TemplateField>
<asp:TemplateField HeaderText="日本語">
<ItemTemplate><asp:TextBox ID="txtJa" Text='<%# Eval("JaText") %>' runat="server" /></ItemTemplate>
</TemplateField>
</Columns>
</asp:GridView>
<asp:Button ID="btnSave" Text="儲存所有語系" OnClick="btnSave_Click" />
protected void btnScan_Click(object sender, EventArgs e)
{
var list = ScanLocalizableControls(this);
gvTexts.DataSource = list;
gvTexts.DataBind();
}
private List ScanLocalizableControls(Control parent)
{
var list = new List();
foreach (Control c in parent.Controls)
{
if (IsLocalizable(c))
{
string key = $"{Request.AppRelativeCurrentExecutionFilePath}_{c.ID}";
list.Add(new LocalizableItem
{
ControlId = c.ID,
ControlType = c.GetType().Name,
ZhText = GetDbText(key, "zh-TW"),
EnText = GetDbText(key, "en-US"),
JaText = GetDbText(key, "ja-JP")
});
}
if (c.HasControls) list.AddRange(ScanLocalizableControls(c));
}
return list;
}
private bool IsLocalizable(Control c)
{
return c is Label || c is Button || c is LinkButton || c is HyperLink
|| c is GridView || c is Literal; // 依需求擴充
}
進階功能
- 匯入/匯出 Excel:方便翻譯廠商離線作業。
- 版本控管:文字變更記錄歷史,支援回滾。
- 預設值回填:DB 無資料時自動抓取控制項原始 Text 寫入 DB,避免空白。
- 快取策略:
MemoryCache 快取頁面文字 10~30 分鐘,減少 DB 查詢。
- GridView/Repeater 標題:掃描時產生虛擬 ControlId 如
gvUsers_Header_Name,對應 BoundField.HeaderText。
效能與安全
| 面向 |
做法 |
| DB 查詢 |
單次查詢撈取整頁所有文字 (WHERE PagePath=@p AND Culture=@c) |
| 快取 |
MemoryCache key: "PageText_{PagePath}_{Culture}",滑動過期 20 分鐘 |
| XSS |
儲存前 HTML Encode,輸出時 Literal/Label 自動編碼 |
| 權限 |
管理介面僅限「最高管理員」角色存取 |
遷移既有專案步驟
- 建立資料表、DbHelper、LocalizedPage 基底類別。
- 逐頁將
Inherits="System.Web.UI.Page" 改為 Inherits="LocalizedPage"。
- 執行一次「掃描」自動建立所有控制項預設文字至 DB。
- 驗證切換語系正常運作。
- 開放管理介面給業務調整術語。