localization

原文: http://phpadvent.org/2010/localization-by-anthony-gentile 翻译:校长 RoyGu http://roygu.com

当我们创建一个网站时,一个非常重要的考虑是:网站的受众/访客。无论网站是销售商品、提供服务亦或是信息资讯,都须考虑当国外用户访问时页面如何呈现。如果他们想在你的网站上购买商品、使用服务或获取资讯,你的网站能够正常服务他们吗?应该提供正常服务吗?本地化让用户界面符合用户的预期,如:日期格式、货币以及本地语言文本。

许多以英语为母语的人并没有意识到这点,尽管英语的普及率很高,但说英语的人仍只占很小比率。这篇文章统计了世界上分布最广的语言是英语,但英语并不是人们说得最多的语言(现在排第三)。互联网上有如此多的有用信息,很大一部分不是用英文描述的。用计算机解决语言相关的问题是一件困难的事情,但是创建一个国际友好的网站并不是我们想象的那么难。虽然今天介绍的方法不能适用于10种不同语言的网站,但是对于需要提供两三种语言的网站非常有效果。比如,在美国,讲西班牙语的人数在显著增加,拥有一个能用西班牙语言交流的电子商务网站,将扩大你的潜在客户群。

在讲如何为网站实现本地化之前,我想花点时间谈论下字符编码。即使你无法预见你的网站是否需要提供本地化功能,了解字符(charsets)编码相关的知识都是非常有必要的。字符编码是用来帮助软件识别用户期望字符集的。例如,通用的ISO 8859-1字符集是西欧的通用字符集。因此,在这种字符集中可以用英文字母表示德语中的原音。但如果我在这种字符集中想显示可拉伯字符,如:ق,那么很可能会显示成� 或 Ù。这就意味着,在ISO 8859-1字符集下,不能正确识别阿拉伯字符。

一种非常友好的跨语言字符编码是兼容ASCII码的UTF-8编码,它代表Unicode字符集,特别是在传统应用中较为常见。在网站中使用UTF-8,其支持的字符非常多,能够最大地满足用户需求。为了正确地实现一种字符编码,你要确保在你整个应用中编码保持一致。如果你使用UTF-8,那么Apache、数据库、PHP以及PHP可用的功能和文档类型都应该设置成UTF-8。如果这些都不是一致的,你最终可能会得到不同字符编码的混合数据,这种问题即使能补救也比较困难。

// Apache httpd.conf or .htaccess

// This will add a charset to the Content-Type response header.
AddDefaultCharset UTF-8

// php.ini
default_charset = "UTF-8"

// Example PHP function
htmlentities($data, ENT_COMPAT, 'UTF-8');

// XML
<?xml version="1.0" encoding="UTF-8" ?>

// HTML
<meta http-equiv="Content-Type"
    content="text/html; charset=utf-8" />

如果你想了解更多关于UTF-8的内容,并且为什么它如此重要,猛击下面的链接:

刚才我们已经讨论了字符编码,接下来我们进入主题,谈谈如何实现本地化。如何判断为用户加载哪个本地化?如何知道用户来自哪里?一种解决方案是根据IP地址判断地域,但是更好的解决方案是让浏览器告诉我们使用哪个本地化。当用户发起HTTP请求时,HTTP头中的Accept-Language将会被发送,我们通常可以从PHP超全局变量$\_SERVER中获取该值。看个示例:

Accept-Language: en-us;en;q=0.5

虽然这是一种检测默认语言的简单方法,但这并不意味着它就是用户期望的语言。所以提供一种简单方法给用户切换语言是非常有必要的。许多开发者认为,在session或cookie中存储本地化编码就足够了,但这种方法不够友好和直接,在URL中加入本地化提示岂不是更好,如:http://example.com/en/blog/http://example.com/de/blog; 或者用子域名来表示,如: http://en.wikipedia.org/.

如何实现呢?不难,在Apache中为网站设置一个重写规则:

RewriteRule ^(en|es)/(.*) /$2 [PT,E=lang:$1]

有了这个重写规则,我们就不需要为每种语言复制一份代码了。我们可以委托Apache查找用户请求的语言,并把它存储在环境变量中,然后转递到代码中。在我们的应用逻辑中,我们可以这样获取本地化编码:

$locale = apache_getenv('lang', true);

这里有一个支持本地化的PHP脚本,仅供参考:

<?php

/**
 * Stick to the codes that are already standard.
 * ISO 3166 - http://en.wikipedia.org/wiki/ISO_3166-1
 * RFC 1766 - http://www.faqs.org/rfcs/rfc1766.html
 */
$accepted_locales = array(

    'en' => 'en_US',
    'es' => 'es_MX'
);


// Get the locale from the browser.
$browser_locale = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);


// For more specific locale codes, e.g., en-us (English, United States), es-mx (Spanish, Mexican), etc.
// $browser_locale = current(explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']));

// Check against a whitelist instead of trusting $_GET['lang'].
// For reference: http://ha.ckers.org/blog/20100128/micro-php-lfi-backdoor/
if (in_array($browser_locale, array_keys($accepted_locales))) {
    $locale_code = $accepted_locales[$browser_locale];
} else {
    $locale_code = 'en_US';
}


// Or, use a URL-based locale designator and a rewrite rule.
// RewriteRule ^(en|es)/(.*) /$2 [PT,E=lang:$1]
// Then, grab that environment variable with PHP.
/*
$locale_code = apache_getenv('lang', true);
if (in_array($locale_code, array_keys($accepted_locales))) {
    $locale_code = $accepted_locales[$locale_code];
} else {
    $locale_code = 'en_US';
}
*/

$locale_entries = array();


// Load the locale file.
if (file_exists($locale_code . '.php')) {
    $locale_entries = include $locale_code . '.php';
}


/*
These locale entries are loaded from files that return an array.
en_US.php
<?php
return array(
    'WELCOME' => 'Hello %s!',
    'GOODBYE' => 'Goodbye!',
);

es_MX.php
<?php
return array(
    'WELCOME' => '¡Hola %s!',
    'GOODBYE' => '¡Adios!',
);

You could store locales in a database as well.
 */

// Now, you can create a simple function that will display the correct text.
function locale($locale_entries, $key, $replacements = array()) {
    if (isset($locale_entries[$key]) && empty($replacements)) {
        return $locale_entries[$key];
    } elseif (isset($locale_entries[$key])) {
        return vsprintf($locale_entries[$key], $replacements);
    }
    throw new Exception("Locale entry for '$key' does not exist.");
}


// echo locale($locale_entries, 'WELCOME', array('Anthony'));
// echo locale($locale_entries, 'GOODBYE');

// A basic class to handle locales
class Locale {

    public $code = 'en_US';
    public $locale_path = '/var/www/';
    protected $_entries = array();

    public function setCode($code)
    {
        $this->code = $code;
        $this->load();
    }

    public function load()
    {
        if (!file_exists($this->locale_path . $this->code . '.php')) {
            throw new Exception("Locale file: {$this->locale_path}{$this->code}.php does not exist.");
        }

        $this->_entries = include $this->locale_path . $this->code . '.php';
    }

    public function fetch($key, $replacements = array())
    {
        if (isset($this->_entries[$key]) && empty($replacements)) {
            return $this->_entries[$key];
        } elseif (isset($this->_entries[$key])) {
            return vsprintf($this->_entries[$key], $replacements);
        }
        throw new Exception("Locale entry {$this->code}:$key does not exist.");
    }
}

$locale = new Locale();
$locale->setCode($locale_code);

//echo $locale->fetch('WELCOME', 'Anthony');
//echo $locale->fetch('GOODBYE');

?>

我希望本文讲述得足够清楚了,能够帮助你养成思考网站访客并最大满足用户的好习惯。我认为绝大多数网站应该使用UTF-8。在项目开始阶段请至少花点时间确保网站是国际友好的。