如何将 PHP 应用程序国际化

了解实现 PHP 国际化使用的工具和策略

对应用程序的本地化或者可以事先进行规划,或者也可以过后追悔莫及。本文带您探索有助于改进本地化过程或进行本地化规划的技术和工具,如gettext、XML、XSLT 及设计模式等。

Robert Bradley (robert.bradley@gmail.com), 顾问, ALDION Consulting Pte Ltd

Robert Bradley 拥有超过 20 年的软件工程经验,目前现在处于半退休状态并潜心于撰稿及咨询。他在样例翻译中使用了有些生疏的德语。



2007 年 4 月 24 日

评估应用程序

本地化的要求非常模糊,如同说 “使应用程序准备好在德国使用” 一样。但是即使当要求看似很详细的时候,您还是可以发现产品经理没考虑到的事情。

例如,就拿清单 1 中所示的标准的 Yahoo! RSS 新闻阅读器应用程序来说。当页面第一次被调用时,将显示默认的标题列表,并且有一个表单字段,您可以在其中键入选择的新闻类别,然后再重新提交页面。该应用程序将验证键入的类别,如果类别无效的话将显示错误消息,或者显示所请求类别的标题。

清单 1. 来自 Yahoo 的 RSS 新闻!
<?php

require_once "XML/RSS.php";

# Normalize and validate user input

$newstype = strtolower(isset($_POST['newstype']) ? $_POST['newstype'] : "tech");
$error = null;
switch($newstype) {
  case 'world':
  case 'tech':
	break;
  default:
	$error = 'Invalid news type.';
	break;
}

?>

<html>
<head>
<title>RSS Feed from Yahoo News</title>
</head>
<body>
<h1>RSS Feed from Yahoo News</h1>

<p>
Welcome to the Yahoo RSS news reader.  To view headlines for different kinds of news
enter the news type in the text box below and then submit the form.  Valid news types
are 'tech' and 'world'.
</p>
<p>
<form method = "POST">

<?php
  if (isset($error)) {
	echo '<br/><font color = "red">' . $error . '</font><br/>';
  }
?>

<input type = "text" name = "newstype" value = "<?php echo $newstype ?>" />
<br>
<input type = "submit">
</form>
</p>
  
<?php
  $url = "http://rss.news.yahoo.com/rss/" . $newstype;
  $rss = new XML_RSS($url);
  $rss->parse();
  echo "<ul>\n";
  foreach ($rss->getItems() as $item) {
	echo "<li><a href=\"" . $item['link'] . "\">" . $item['title'] . "</a></li>\n";
  }
  echo "</ul>\n";
?>
  
</body>
</html>

虽然此应用程序很简单,但是它说明了在本地化过程中出现的很多问题。它还反映了任何脚本语言的一些优缺点。

您总是可以按成本价迅速推出产品。 用户界面 (UI)、业务逻辑和配置,或缺少其中一些元素,将它们混合在一起,而不考虑维护或扩展具有新功能的页面。

在对此页面进行德语本地化以及可能的其他语言本地化时,会遇到几个障碍:

  • 应当重构代码以分离各个应用层。
  • 必须提取并翻译 UI 文本,例如标题、内容和错误消息。
  • 必须将诸如 Submit 按钮之类的表单控件本地化。
  • 应用程序需要一种定义目标语言环境的方法。
  • 用户输入要求及验证会因语言环境的不同而有所变化。
  • 考虑依赖于语言环境的业务规则,例如 Yahoo! RSS 提要的 URL;目标和语法会因国家或地区的不同而有所不同。
  • 对在各种语言环境中支持配置和控制应用程序域行为的框架的需要更加迫切。

完成所有这些工作所需的投入很容易与原始应用程序的成本持平,或超出原始应用程序的成本 —— 全面考虑需要面对很多困难。 因此,您的团队必须对这些障碍有所准备,无论是在应用程序完成之前还是完成之后。


本地化术语

如果您要在项目中与翻译人员协作,那么最好确保您了解一些可能在与翻译人员的谈话中会遇到的术语。表 1 定义了语言学家和专业翻译人员使用的一些常用术语。

表 1. 本地化术语
术语含义
语言环境语言与国家或地区的组合 —— 例如 en_US、en_GB、de_DE
国际化 (I18N)规划、设计和准备要本地化的应用程序
本地化 (L10N)识别并翻译 (T9N) 用于目标语言环境的应用程序 UI
外在化(字符串提取)提取程序字符串以供翻译的过程

花样翻新的本地化

清单 2 显示了经过重构翻新以支持本地化后的 RSS 应用程序。功能逻辑和本地化框架被抽象到处理程序类中。主代码中需要完成的工作是构造页面处理程序对象、HTML 模板并调用处理程序的 accessor 方法来填充 HTML 模板的动态部分。

清单 2. 经过本地化的 RSS 新闻
<?php
# news2.php
require_once "RSSHandler.php";
$handler = new RSSHandler;
?>

<html>
  <head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
    <title><?php echo $handler->getTitle() ?></title>
  </head>
  <body>
    <h1><?php echo $handler->getTitle() ?></h1>
    <p>
      <?php echo $handler->getGreeting() ?>
    </p>
    <?php $handler->showerror() ?>
    <form method = "POST">
      <input type = "text" name = "newstype" 
        value = "<?php echo $handler->getNewstype() ?>" />
      <br/>
      <input type = "submit" 
        value="<?php echo $handler->getSubmitbutton() ?>" />
    </form>
    <?php $handler->showHeadlines() ?>
  </body>
</html>

重构的优点是程序员可以更轻松地维护代码。也就是说,UI 现在是否更具有可维护性是有疑义的,因为它缺少语言环境,并且 HTML 的绝大部分仍嵌入在 PHP 代码中。

PHP 程序员是否会信任 Web 开发人员所做的这些类型的更改?让后端开发人员处理小的 UI 更改请求需要花费多少成本?稍后,我将展示扩展样式表语言转换(Extensible Stylesheet Language Transformation,XSLT)如何更好地分离应用层。

构造函数

让我们从处理程序类的构造函数开始(参见 清单 3)来看看如何为本地化翻新页面。当页面处理程序被实例化后,构造函数将确定相应的语言环境,检索该语言环境的配置设置,验证任何用户输入,根据用户当前的语言环境计算所有以前的静态内容,然后验证任何用户输入。(要获得源代码,请参阅 下载。)

清单 3. RSSHandler.php
  public function __construct() {

    # set up locale
    putenv("LANG=de_DE");       # for the configuration
    setlocale(LC_ALL, 'de_DE');   # for gettext

    # Get locale-dependent configuration
    $this->conf = new RSSConfig;
    $this->configdecorator = new Configdecorator($this->conf);

    # set up the text extraction context
    bindtextdomain($this->conf->getTextdomainmessage(),  
      $this->conf->getTextdomainpath());
    textdomain($this->conf->getTextdomainmessage());
    
    # Extract the strings for translation.
    $errormsg = gettext("News type is not valid.");
    $this->title = gettext("Yahoo RSS News");
    $this->submitbutton = gettext("Get the news.");
    $lgreeting = 
      gettext("Welcome to the Yahoo RSS news reader. ") . " " .
      gettext("Enter a news type, then submit the form.") . " " .
      gettext ("Valid news types are: %s.");
    $decoratedtypes = $this->configdecorator->getNewsTypes();
    $this->greeting = sprintf($lgreeting, $decoratedtypes);
    
    # Normalize and validate 
    $this->newstype = $this->normalize();
    $this->error = $this->validate() ? '' : $errormsg;
  }

在本例中,为了简单起见,语言环境被硬编码为 de_DE。在现实情况中,您会需要根据业务要求设置语言环境。确定正确的语言环境有两种常用方法:检查请求 URL(例如,www.ebay.de、amazon.fr)或简单地询问用户并在其浏览器中设置首选 cookie。

检索配置参数

确定了语言环境后,处理程序将检索德国配置参数。在本例中,配置包括使用 PEAR 软件包 (Config.php)、XML 配置文件 (RSSConfig.xml)、特定于应用程序域的包装器 (RSSConfig.php) 和一个简单的装饰类来为一些配置参数添加 HTML 标记 (Configdecorator.php)。

清单 4 中所示的配置文件将以 XML 形式存储其信息。它用于指定字符串提取的上下文(//conf/textdomain)—— 依赖于语言环境的组件的位置及消息文件的名称。

注:如果下载并尝试运行样例,请确保更改路径使其指向提取并翻译后的字符串以匹配您的环境。

配置文件还将告诉应用程序如何构造正确的 Yahoo! 新闻提要 URL (//conf/de_DE/baseurl)、如何验证用户对新闻提要类型做出的选择 (//conf/de_DE/newtypes) 以及在用户没有做出选择的情况下哪类新闻应当为默认值。

清单 4. Configuration.xml
<?xml version="1.0" encoding="UTF-8"?>
<conf>
    <textdomain>
    	<path>/home/bbradley/Sites</path>
	<messagefile>messages</messagefile>
    </textdomain>
    <en_US>
        <baseurl>http://rss.news.yahoo.com/rss/</baseurl>
        <newstypes>
            <type>
                <default>true</default>
                <name>tech</name>
                <url>tech</url>
                </type>
            <type>
                <default/>
                <name>world</name>
                <url>world</url>
                </type>
        </newstypes>
    </en_US>
    <de_DE>
        <baseurl>http://de.news.yahoo.com/</baseurl>
        <newstypes>
            <type>
                <default>true</default>
                <name>Ausland</name>
                <url>politik/ausland.html.xml</url>
                </type>
            <type>
                <name>Technik</name>
                <url>technik/index.html.xml</url>
                </type>
        </newstypes>
    </de_DE>
</conf>

您可以通过添加 “默认” 语言环境使配置更加健壮。然后,如果应用程序尝试显示无效的语言环境,则要始终保证一些合理的应用程序行为。您还可以扩展配置以容纳其他应用程序形式,例如品牌联合或基于用户权限的应用程序限制。

余下的大多数处理程序对象包括衍生页面中与每种语言环境相对应的文本部分,并且提供公共的 accessor 方法,这些方法使这些经过翻译的文本可用于 HTML 模板。

gettext 提取框架

应用程序将使用一个名为 gettext 的开源字符串提取框架以实例化经过翻译的字符串。此框架有几个优点:

  • 它是免费的。
  • 它可以在各种平台上运行。
  • 它可以使用许多编程语言,包括 PHP。
  • 它特别适用于翻新工作。

要使用 gettext,请执行以下步骤:

  1. 为必须支持的所有语言环境创建目录结构。
  2. 通过将字符串移入特定函数 gettext() 的参数来标记字符串。
  3. 运行 xgettext 命令行实用程序将字符串提取到消息文件中。
  4. 将得到的消息文件复制到每个语言环境的专用目录下。
  5. 编辑本地版本的消息文件并翻译文本。
  6. 运行 msgfmt 命令行实用程序以创建在运行时使用的本地化的消息数据库。

使用 gettext() 函数的目的很明显 —— 在运行时检索本地化的文本,认识到这一点很重要。但同等重要的是事实上它还是用于文本提取的 标记xgettext 实用程序将查找此函数标记的字符串。

清单 5 中显示了样例应用程序的所有软件组件,包括在应用程序配置中定义的目录结构。每个 gettext 实现都要求使用与此类似的目录结构。无需为默认语言环境创建目录树或消息数据库;用于该语言环境的字符串已经被嵌入到代码中。但是需要编辑 RSSConfig.xml 文件以正确标识指向 gettext 框架中这些文件的完整路径。

清单 5. 目录结构
ConfigDecorator.php -- Decorator class
de_DE -- Directory for Germany
	LC_MESSAGES -- Messages sub-directory
		messages.mo -- Compiled messages database
		messages.po -- Translated messages
messages.po -- Generated by xgettext
news2.php -- Main application / HTML template
RSSConfig.php -- Application configuration class
RSSConfig.xml -- Application configuration file
RSSHandler.php -- HTTP request handler / business logic

翻译消息文件

在德语消息文件中,翻译人员必须提供的部分被标记为粗体。换言之,翻译人员必须填空。当翻译人员首次打开消息文件时,这些部分将为空,并且翻译人员必须指定要使用的字符编码(在本例中为 Latin-1)和翻译。消息 ID (msgid) 是用于提取而调用 gettext() 函数进行标记的原始文本。 消息字符串 (msgstr) 是必须用目标语言显示的字符串。清单 6 显示了当用户指定了错误的消息类型时错误消息的翻译。

清单 6. 德语消息文件 messages.po.de
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2006-11-05 11:05-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ISO-8859-1\n"
"Content-Transfer-Encoding: 8bit\n"

#: RSSHandler.php:31
msgid "News type is not valid."
msgstr "Die Nachrichtkategorie ist falsch."

如果任何已提取的字符串的文本在任何时候发生更改,则在每次生成消息文件时将创建新消息 ID。每次添加或更改字符串时,都应当重新生成消息文件,否则将在目标语言环境显示未翻译的新文本。

在生产环境中,L10N 团队通常会执行所有这些任务(除了准备源代码以外)。但是这不意味着您可以完全不受语言问题的影响。将应用程序本地化时需要切记以下要点:

  • 不要将功能信息嵌入到字符串中。
  • 在完整的句子中使用 printf 标记来标记动态内容而不要将句子分割为多个字符串。记住不同语言之间的句子中的语序是不同的;您可以让翻译人员来控制语序。如果字符串包含多个动态元素,则 msgfmt 支持扩展标记语法,该语法允许您指定字符串中每个动态元素的位置。在翻译过程中可以轻松地更改元素的顺序。您可以在任意一本 gettext 的联机手册中了解关于位置语法的更多信息(请参阅 参考资料)。
  • 确保字符串具有足够的有意义的上下文。记住翻译人员将只能看到字符串 —— 而不是运行的应用程序。

注:有关将软件本地化时在语言方面的更多建议,请参阅 参考资料

当添加新文本或重述现有文本时,本地化团队可以使用其他 gettext 实用程序将新字符串或更改的字符串合并到现有消息文件中,从而免去重新翻译的需求。


设计本地化

在大多数情况下按照上述方法处理本地化很可能足够优秀。但是,随着范围的扩大,考虑使用设计模式来设计应用程序会十分有意义。最广泛使用的一种模式是 Model-View-Controller (MVC)。此模式鼓励您分层(表示层、域层、数据访问层)处理应用程序。MVC 模式的另一个优点是使用它可以更轻松地扩展控制器和视图来处理其他类型 HTTP 请求,可以是 SOAP 服务,也可以是 XML API。

RSS MVC 模式

Struts、Ruby on Rails 和 Zend 框架全都拥护这种分层设计理念。如果发现 MVC 方法很吸引人,请研究用于 PHP 的 Zend 框架。但是,为了减少样例应用程序要求使用的附加软件包的数目,我启动了自己的 MVC 模式,如 清单 7 所示。

现在主代码的大小减少了更多。它起到控制器的作用,该控制器将委派完成大多数工作来处理 HTTP 请求。它将构造一个域对象,该域对象将利用已经讨论的相同业务规则,从域中检索会话数据模型,然后将该模型转换为视图类以转换为 HTML,该 HTML 将发送回客户机。

清单 7. RSS 新闻控制器 news3.php
<?php
# news3.php
require_once "View.php"; 
require_once "RSSDomain.php";

putenv("LANG=de_DE");
$domain = new RSSDomain;
$model = $domain->getModel();
$view = new View($domain->getTemplate(), $model);
echo $view->asHTML();
?>

以前设计的相同的应用程序配置模式仍然控制着业务规则行为。改变的只是新表示层处理 UI 的方法。该层不再使用 PHP 的 HTML 模板支持。相反,它依靠 XSL 转换。域模型是一个可以序列化为 XML 的嵌套对象的递归数据结构。视图将导致 XSL 模板将会话数据的 XML 表示转换为返回到客户机浏览器的本地化的 HTML。

表示层

清单 8 中显示了构成表示层核心的 XML 模型和 XSL 模板。模型包括配置信息,例如新闻类型,尤其是这些选择可以显示给用户的方法(//rss/newstypes/displaytypes)。模型还返回用户的新闻类型选择(//rss/userinput/newstype)和检索的新闻标题及 URL(//rss/headlinelist/headline)。在用户选择的新闻类型无效的情况下,模型将返回一个错误代码,而不是返回标题。XSL 模板将访问返回的数据模型部分以构建显示在客户机浏览器中的 HTML。

清单 8. 样例 XML 模型和 XSL 模板 (RSSView.xsl)
 <rss>
 	<newstypes>
		<displaytypes>Ausland, Technik</displaytypes>
		<typelist>
			<type>Technik</type>
			<type>Ausland</type>
		</typelist>
	</newstypes>
	<userinput>
		<newstype>Ausland</newstype>
	</userinput>
	<headlinelist>
		<headline>
		<hlink>http://de.news.yahoo.com/01112006/12/guatemala.html</hlink>
		<htitle>Guatemala und Venezuela halten Besprechungen</htitle>
		</headline>
		<headline>
		<hlink>http://de.news.yahoo.com/01112006/12/mandat.html</hlink>
		<htitle>Mandat einer Regierung in Afrika</htitle>
		</headline>
	</headlinelist>
</rss> 

---------------------------------------------------------------

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:variable name="pagetitle">Yahoo Schlagzeilen</xsl:variable>
<xsl:variable name="submitbutton">Hol Schlagzeilen</xsl:variable>
<xsl:variable name="displaytypes">
<xsl:value-of select="/rss/newstypes/displaytypes"/>
</xsl:variable>
<xsl:variable name="newstype">
<xsl:value-of select="/rss/userinput/newstype"/>
</xsl:variable>

<xsl:template match="/">
<html>
	<head>
		<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
		<title>
			<xsl:value-of select="$pagetitle"/>
		</title>
	</head>
	<body>
		<h1><xsl:value-of select="$pagetitle"/></h1>
		<p>
		Willkommen! Um den Yahoo Nachrichtleser zu gebrauchen, gib einen von den
		folgenden Nachrichttypen ein:  \
		<xsl:value-of select="/rss/newstypes/displaytypes"/>!
		</p>
		<!-- errors  -->
		<xsl:choose>
			<xsl:when test="rss/userinput/error/code">
				<p><b><font color="red">Falscher Typ</font></b></p>
			</xsl:when>
		</xsl:choose>
		<p>
		<form method = "POST">
			<input type = "text" name = "newstype" value = "{$newstype}" />
			<br/>
			<input type = "submit" value="{$submitbutton}" />
		</form>
		</p>
		<!-- headlines  -->
		<xsl:for-each select="/rss/headlinelist/headline">
			<a><xsl:attribute name="href">
			<xsl:value-of select="hlink"/>
			</xsl:attribute><xsl:value-of select="htitle"/></a>
			<br/>
		</xsl:for-each>
	</body>
</html>
</xsl:template>
</xsl:stylesheet>

使用这种方法,经过本地化的字符串不是通过诸如 gettext 之类的工具提取的。相反,每种语言环境都有自己的 XSL 模板。由于语言环境不再被提取,因此它应当可以更轻松地分布维护应用程序的工作量。Web 开发人员、本地化团队甚至产品经理都可以承担实现适度的错误修正的职责并将请求限定到 UI

视图类使用 PHP V5 附带的 XSL 支持、数据模型和 XSL 模板来构建 HTML。PHP V4 则以一种不同的方法来实现此功能。在需要将旧的应用程序本地化的情况下,该方法也以注释的形式显示。在大多数情况下,XSLT 都会默认处于启用状态,但您可能需要在 PHP 安装中启用 XSLT 处理以使其全部运行。清单 9 显示了 XSL 转换。

清单 9. XSL 转换 (View.php)
	public function asHTML() {
		$xml = new DomDocument;
		$modelxml = $this->model->asXML();
		#system('echo " ' . $modelxml . ' " > /tmp/l.txt');
		$xml->loadXML($modelxml);
		$xsl = new DomDocument;
		$xsl->load($this->template);
		$proc = new xsltprocessor;
		$proc->importStyleSheet($xsl);
		$html = $proc->transformToXML($xml);
		
#      Following is XSL transformation syntax for PHP4
#		$xh = xslt_create();
#		$arguments = array ('/_xml' => \
$this->model->asXML(),  '/_xsl' => $this->template);
#		$html = xslt_process($xh, 'arg:/_xml', $this->template, 
#                                          NULL, $arguments, $this->parameters);
#		xslt_free($xh);

		return $html;
	}

在包括了较大型团队的情况下,提取字符串并通过主模板生成 XSL 模板可能仍有意义。这通常是由全球化管理系统 (GMS) 来执行的。然后 Web 开发人员只在以企业首选语言表示的默认 XSL 模板上工作,并且翻译人员将使用管理系统的工具,并可能使用计算机辅助翻译系统(如 SYSTRAN)来完成工作。

开发结束后并且到了发布产品的时刻,典型的本地化过程可能会是这样:

  • GMS 将从核心 XSL 模板中提取所有文本并且更新翻译数据库。
  • L10N 团队将使用翻译数据库来翻译新字符串并且重新翻译更改的字符串。
  • GMS 通过翻译数据库重新构建每种语言环境的本地化 XSL 模板。

虽然使用 GMS 会带来好处,但是对于一个小的团队或变化不大的成熟的应用程序来说,使用 GMS 可能没有什么意义。对于所提及的所有工具来说,您应该清楚获取、实现和支持您决定使用的工具的收益和开销。


结束语

类似本文的简短文章无法全面讲述您可能遇到的每个挑战。例如,我尚未讨论如何处理字符编码、日期、货币、统计数字、地址和电话号码。在大多数情况下,您必须为一种语言环境正确设定日期和货币的格式并正确显示日期和货币。您还必须了解当国际用户在表单中键入货币量和日期时如何验证和解释货币量和日期。

但是提供的技术使您可以完成将应用程序本地化 80% 的工作。如果要处理预先存在的应用程序并且有时间和预算的限制,则可以着重于要求不高的翻新方法。如果要开发新应用程序或者拥有足够的资源来将现有应用程序本地化,允许的情况下,一定要敢于更加勇往直前。您不但会得到一个经过良好本地化的产品,而且无论是要修正错误,还是要添加功能,都会使应用程序维护人员的工作更加轻松。


下载

描述名字大小
样例脚本1os-php-intl.zip12KB

注意:

  1. .tar 文件中包含除去必要的 PEAR 模块 XML_RSS 和 Config 以外的完整源代码。您还必须确保 XSLT 已在 PHP 安装中启用。

参考资料

学习

获得产品和技术

  • 使用 IBM 试用软件 改进您的下一个开放源码开发项目,这些软件可以通过下载或从 DVD 中获得。

讨论

  • developerWorks PHP Developer Forum 这里是所有 PHP 开发人员讨论各种主题的地方。在这里您也可以发布您关于 PHP 脚本、函数、语法、变量、PHP 调试及与 PHP 开发人员相关的所有其他主题的问题。
  • 通过参与 developerWorks blog 加入 developerWorks 社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=194770
ArticleTitle=如何将 PHP 应用程序国际化
publish-date=04242007