ASP.NET 2.0 引入強大的 **資源檔** 機制,配合 `CultureInfo`、`UICulture`、`Profile`、`InitializeCulture()`,能以最少代碼實現完整的 **多語言/當地語系化 (Globalization/Localization)**。本文從基礎概念到實戰步驟、進階技巧、常見坑點完整解析。
| 術語 | 說明 |
|---|---|
| Globalization (全球化) | 應用程式支援多種文化特性 (日期、貨幣、數字格式、排序規則) 的設計過程。 |
| Localization (當地語系化) | 針對特定文化/語言調整資源 (字串、圖片、版面) 的過程。 |
| Culture (文化特性) | 決定日期/時間/數字/貨幣格式 (`Thread.CurrentThread.CurrentCulture`)。 |
| UICulture (UI 文化特性) | 決定資源管理器載入哪個語言資源檔 (`Thread.CurrentThread.CurrentUICulture`)。 |
| Neutral Culture | 僅語言,無區域 (如 `en`, `zh`)。 |
| Specific Culture | 語言 + 區域 (如 `en-US`, `zh-TW`, `zh-CN`)。 |
| InvariantCulture | 文化無關 (以英文為基準),用於內部儲存、序列化、比較,不可用於 UI 顯示。 |
| 類型 | 存放位置 | 命名範例 | 適用範圍 |
|---|---|---|---|
| 全域資源 | `App_GlobalResources\` | `Resources.resx`, `Resources.zh-TW.resx` | 跨頁面共用字串、圖片、類別 |
| 區域資源 | `App_LocalResources\` | `Default.aspx.resx`, `Default.aspx.zh-TW.resx` | 單一頁面專用資源 |
命名鐵律:基底檔 (無文化代碼) **必須存在** (如 `Resources.resx`),否則強型別類別不會產生,編譯失敗。文化特定檔為選用。
<system.web>
<globalization uiCulture="auto" culture="auto" /> <!-- 自動依 Accept-Language 設定 -->
<anonymousIdentification enabled="true" />
<profile>
<properties>
<add name="LanguagePreference" type="String" defaultValue="auto" allowAnonymous="true" />
</properties>
</profile>
</system.web>
public class PageBase : System.Web.UI.Page {
protected override void InitializeCulture() {
base.InitializeCulture();
string lang = Profile.LanguagePreference; // 從 Profile 讀取
if (string.IsNullOrEmpty(lang) || lang == "auto") {
// 首次造訪:依瀏覽器語言
if (Request.UserLanguages != null && Request.UserLanguages.Length > 0) {
lang = Request.UserLanguages[0];
Profile.LanguagePreference = lang; // 寫回 Profile
} else {
lang = "en-US"; // 最終預設
}
}
// 設定當前執行緒文化特性
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(lang);
}
}
所有 `.aspx.cs` 繼承 `PageBase` 而非 `Page`。
<asp:DropDownList ID="ddlLang" runat="server" AutoPostBack="true" OnSelectedIndexChanged="ddlLang_SelectedIndexChanged">
<asp:ListItem Value="auto" Text="自動 (瀏覽器)" />
<asp:ListItem Value="en-US" Text="English" />
<asp:ListItem Value="zh-TW" Text="繁體中文" />
<asp:ListItem Value="zh-CN" Text="简体中文" />
</asp:DropDownList>
protected void ddlLang_SelectedIndexChanged(object sender, EventArgs e) {
Profile.LanguagePreference = ddlLang.SelectedValue;
Response.Redirect(Request.Url.AbsolutePath); // 重新載入頁面套用新文化特性
}
全域資源 (強型別):
// App_GlobalResources\Resources.resx (基底)
Name: WelcomeText, Value: "Welcome"
// App_GlobalResources\Resources.zh-TW.resx
Name: WelcomeText, Value: "歡迎光臨"
// 頁面中使用 (IntelliSense 支援)
<asp:Label runat="server" Text="<%$ Resources:Resources, WelcomeText %>" />
// 或程式碼後置
lblWelcome.Text = Resources.Resources.WelcomeText;
區域資源 (隱含/顯式運算式):
<!-- 隱含運算式:meta:resourcekey 對應資源檔同前綴鍵 -->
<asp:Label ID="lblTitle" runat="server" meta:resourcekey="lblTitle" />
<!-- 資源檔需含:lblTitle.Text, lblTitle.ToolTip 等 -->
<!-- 顯式運算式:指定資源檔與鍵 -->
<asp:Label runat="server" Text="<%$ Resources:Resources, FooterText %>" />
public class ImageHandler : IHttpHandler {
public void ProcessRequest(HttpContext context) {
string lang = ((ProfileCommon)context.Profile).LanguagePreference;
if (!string.IsNullOrEmpty(lang) && lang != "auto") {
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
}
Bitmap bmp = Resources.Resources.Logo; // 強型別存取
context.Response.ContentType = "image/png";
bmp.Save(context.Response.OutputStream, ImageFormat.Png);
}
public bool IsReusable => false;
}
<asp:Image runat="server" ImageUrl="~/ImageHandler.ashx" />
<!-- Web.sitemap -->
<siteMap enableLocalization="true">
<siteMapNode url="~/Default.aspx" title="Home" description="Home Page" resourceKey="HomeNode" />
</siteMap>
<!-- App_GlobalResources\Web.sitemap.resx -->
Name: HomeNode.Title, Value: "Home"
Name: HomeNode.Description, Value: "Home Page"
<!-- App_GlobalResources\Web.sitemap.zh-TW.resx -->
Name: HomeNode.Title, Value: "首頁"
Name: HomeNode.Description, Value: "首頁說明"
| 功能 | ASP.NET 1.1 | ASP.NET 2.0 |
|---|---|---|
| 資源檔存取 | `ResourceManager` 手動 `GetString` | 強型別 `Resources.Class.Key`、運算式建構器 `<%$ Resources:... %>` |
| 文化特性設定 | `Application_BeginRequest` 手動設定 Thread | `Page.InitializeCulture()` 虛擬方法、自動 Profile 整合 |
| 瀏覽器語言偵測 | 手寫 `Request.UserLanguages[0]` | ` |
| 使用者偏好持久化 | Session 手動管理 | Profile Provider 自動持久化 (匿名/登入皆支援) |
| 區域資源 | 不支援 | `App_LocalResources` + `meta:resourcekey` |
| 非字串資源 (圖片等) | 不支援 | 資源檔支援任意型別 + 自訂 Handler |
| 坑點 | 現象 | 解決 |
|---|---|---|
| 基底資源檔遺失 | 編譯錯誤:找不到 `Resources` 類別 | 確保 `App_GlobalResources\Resources.resx` (無語言代碼) 存在 |
| 區域資源檔遺失 | 隱含運算式編譯錯誤 / 顯式運算式無效果 | 確保 `App_LocalResources\Page.aspx.resx` 存在 |
| 鍵名不一致 | 運算式建構器報錯 / 顯示預設語言 | 文化特定檔鍵名**必須與基底檔完全一致** |
| ImageHandler 執行緒文化特性錯誤 | 圖片語言不跟隨切換 | Handler 中**必須重新設定** `CurrentUICulture` (非頁面執行緒) |
| Profile 未啟用匿名識別 | 未登入使用者語言偏好不保存 | ` |
| 編碼問題 | 中文顯示亂碼 | 儲存 `.resx` 為 UTF-8 (VS 預設);` |
ASP.NET 2.0 的資源檔 + Profile + `InitializeCulture()` 架構在當年極具前瞻性,完整解決了「偵測、持久化、切換、當地語系化、非字串資源」全鏈路問題。理解其設計精髓,對遷移至現代框架同樣受用。