动态网站多语言管理






3.49/5 (40投票s)
2007年8月18日
5分钟阅读

83347

2425
一种智能的方式来管理多语言网站的语言,使用资源和全球化、本地化
引言
一个可以支持多种语言并根据特定地理位置的文化进行更改的网站已不再是一个新话题。尽管有许多文章对该主题进行了很好的解释,但我仍然认为其中许多对“初学者”来说很复杂,而且没有一篇提供一种动态的方式来管理网站上可用的语言。本文旨在以更动态的方式来处理有关全球化/本地化概念的语言管理。
背景
使用资源和全球化/本地化可能不是在网站中提供多语言支持的最佳选择。然而,资源有一个优点是也可以存储二进制对象(例如图像),并且 ASP.Net 可以通过 Expression 来映射网页上的控件与资源。
因此,而且由于本文旨在针对“初学者”而不是有经验的开发人员,我将非常乐意听取所有有价值的建议。感谢您的关注和支持。
现在,回到大多数开发人员或客户可能提出的要求:
1. 网站必须能够切换到其他语言。在本文的范围内,将使用 `resource`。
并且更改应该只影响当前访问者的浏览器。您可能会想到 `SESSION`,当然。
2. 法国(法国)的法语和加拿大说的法语有时是不同的,英式英语和美式英语也是如此。ASP.Net 引入了一个称为 `CultureInfo` 的概念。
3. 如果特定语言(访问者选择的)的语言文件(资源文件)没有为网站特定位置所需的显示文本值,会发生什么?幸运的是,ASP.Net 引入了一个称为 `FallBack` 的概念。有一个资源文件作为其他文件的备份(或默认)。如果在选定的资源中找不到键,ASP.Net 将从那里获取值。
4. 开发人员希望将语言文件(资源)放入易于管理的文件夹结构中,但网站应该能够检测当前可用的语言(通过查看可用的语言文件),并且它应该理解哪个是语言文件,哪个不是。如何做到?下面的部分将更详细地解释如何实现这一点,以及一种创建多语言网站的简便方法。
使用资源
首先,让我们创建资源文件(*.resx)并将其放入不同的文件夹中,但这些文件夹应该在 ASP.Net 2.0 的 **`App_GlobalResources** 文件夹内。
因为语言也取决于地理位置。例如:从语言学上讲,加拿大法语与法国说的法语有很大不同。因此,需要将语言与所使用的特定区域相关联,这通过使用 *locale*(语言+位置)来实现。因此,我们的资源文件名必须是 **PrefixName.language-location.resx** 格式。例如:`fr` 是法语代码。`fr-FR` 表示法国的法语。所以 fr 只指定语言,而 fr-FR 是地区。同样,`fr-CA` 定义了另一个地区,表示加拿大的法语和文化。这里我们有 **lang.fr-FR.resx**。
然后,我们需要一种方法在用户动态更改文化时在运行时加载它们。幸运的是,在 ASP.NET 2.0 中实现这一点非常容易。请参见下面的代码。
String welcome = Resources.lang.Welcome;
但是,本地化页面上 UI 控件的推荐方法是在 VS IDE 中设置。选择标签控件,转到 `Properties` 窗口,选择 `Expressions->Text`。然后从下拉列表中选择 `Resources`,并输入类名和 `Resource key`。
检测并加载可用语言
现在我们需要一种智能的方式来检测我们网站上可用的语言文件,并且当访问者切换到另一种语言时,所有网页中的文本也应该随之更改。
我们构建一个类来管理它们,称之为 `LanguageManager`。
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
/// <summary>
/// Class to manage language on the website
/// </summary>
public sealed class LanguageManager
{
/// <summary>
/// Default CultureInfo
/// </summary>
public static readonly CultureInfo DefaultCulture = new CultureInfo("en-US");
/// <summary>
/// Available CultureInfo that according resources can be found
/// </summary>
public static readonly CultureInfo[] AvailableCultures;
static LanguageManager()
{
//
// Available Cultures
//
List<string> availableResources = new List<string>();
string resourcespath = Path.Combine(System.Web.HttpRuntime.AppDomainAppPath, "App_GlobalResources");
DirectoryInfo dirInfo = new DirectoryInfo(resourcespath);
foreach (FileInfo fi in dirInfo.GetFiles("*.*.resx", SearchOption.AllDirectories))
{
//Take the cultureName from resx filename, will be smt like en-US
string cultureName = Path.GetFileNameWithoutExtension(fi.Name); //get rid of .resx
if (cultureName.LastIndexOf(".") == cultureName.Length - 1)
continue; //doesnt accept format FileName..resx
cultureName = cultureName.Substring(cultureName.LastIndexOf(".") + 1);
availableResources.Add(cultureName);
}
List<CultureInfo> result = new List<CultureInfo>();
foreach (CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
//If language file can be found
if (availableResources.Contains(culture.ToString()))
{
result.Add(culture);
}
}
AvailableCultures = result.ToArray();
//
// Current Culture
//
CurrentCulture = DefaultCulture;
// If default culture is not available, take another available one to use
if (!result.Contains(DefaultCulture) && result.Count>0)
{
CurrentCulture = result[0];
}
}
/// <summary>
/// Current selected culture
/// </summary>
public static CultureInfo CurrentCulture
{
get { return Thread.CurrentThread.CurrentCulture; }
set
{
Thread.CurrentThread.CurrentUICulture = value;
Thread.CurrentThread.CurrentCulture = value;
}
}
}
现在我们可以添加尽可能多的资源文件,而不必担心我们的网站有多少种语言。该类将帮助检测这一点。
由于此类应在所有网页中使用,我们不希望在每个页面上重复使用它的代码,因此我们创建了一个 `PageBase` 类。这里将使用 Session 来保存选定的语言状态。
using System.Globalization;
using System.Web.UI;
public class PageBase : Page
{
private const string SESSION_KEY_LANGUAGE = "CURRENT_LANGUAGE";
protected override void InitializeCulture()
{
base.InitializeCulture();
//If you would like to have DefaultLanguage changes to effect all users,
// or when the session expires, the DefaultLanguage will be chosen, do this:
// (better put in somewhere more GLOBAL so it will be called once)
//LanguageManager.DefaultCulture = ...
//Change language setting to user-chosen one
if (Session[SESSION_KEY_LANGUAGE] != null)
{
ApplyNewLanguage((CultureInfo) Session[SESSION_KEY_LANGUAGE]);
}
}
private void ApplyNewLanguage(CultureInfo culture)
{
LanguageManager.CurrentCulture = culture;
//Keep current language in session
Session.Add(SESSION_KEY_LANGUAGE, LanguageManager.CurrentCulture);
}
protected void ApplyNewLanguageAndRefreshPage(CultureInfo culture)
{
ApplyNewLanguage(culture);
//Refresh the current page to make all control-texts take effect
Response.Redirect(Request.Url.AbsoluteUri);
}
}
所有继承自 PageBase 的页面都无需关心访问者选择什么语言。`InitializeCulture` 方法在页面生命周期的早期就被调用。每当访问者选择一种新语言时,我们只需调用基类中实现的受保护方法 `ApplyNewLanguageAndRefreshPage`。
if (ddlLanguages.Items.Count > 0) //make sure there is a SelectedValue
{
ApplyNewLanguageAndRefreshPage(new CultureInfo(ddlLanguages.SelectedValue));
}
我们的全球化框架已准备就绪。现在唯一剩下的就是将特定于资源的添加到资源文件中。对于每种文化,都需要有一个单独的(且命名适当的)资源文件。这个过程就是本地化。在我的 `web.config` 文件中,我使用了以下属性:
<globalization responseEncoding"=utf-8" requestEncoding="utf-8"
fileEncoding="utf-8" />
请注意编码属性:`utf-8`(8 位 Unicode 转换格式)的使用是因为
它是可变长度字符编码,除了 ASCII 兼容之外,还可以表示越南语(本文示例代码)、希腊语、阿拉伯语等语言。有关更多信息,请参阅此链接:
http://en.wikipedia.org/wiki/UTF-8
关注点
如果您使用 Resharper,可以下载一个支持本地化的插件。
这是一个非常酷的插件,可以帮助您检测和重构所有非资源文本,并将它们移到资源文件中。
历史