关于php:最佳实践多语言网站

Best practice multi language website

我已经在这个问题上挣扎了好几个月了,但我还没有处于一个我以前需要探索所有可能的选择的境地。现在,我觉得是时候了解可能性并创造自己的个人偏好,以便在即将到来的项目中使用了。

让我先概述一下我要找的情况

我将要升级/重新开发一个内容管理系统,我已经用了很长时间了。但是,我觉得多语言是这个系统的一个很大的改进。在我没有使用任何框架之前,我将在即将到来的项目中使用laraval4。Laravel似乎是一种更简洁的PHP代码编写方法的最佳选择。Sidenote: Laraval4 should be no factor in your answer。我正在寻找平台/框架独立的一般翻译方法。

应该翻译什么

由于我正在寻找的系统需要尽可能的用户友好,管理翻译的方法应该在CMS内部。不需要启动FTP连接来修改翻译文件或任何HTML/PHP解析的模板。

此外,我正在寻找最简单的方法来翻译多个数据库表,可能不需要制作额外的表。

我自己想出了什么主意

因为我一直在寻找,阅读和尝试自己的东西。我有几个选择。但我仍然不觉得我已经达到了一个最佳实践方法为我真正追求的。现在,这就是我想到的,但是这个方法也有它的副作用。

  • PHP解析的模板:模板系统应该由PHP解析。这样我就可以将转换后的参数插入到HTML中,而不必打开模板并修改它们。除此之外,通过php解析模板,我可以为完整的网站提供一个模板,而不是为每种语言(我以前有过)提供一个子文件夹。达到这个目标的方法可以是smarty、templatePower、laravel的blade或任何其他模板解析器。正如我所说,这应该独立于书面解决方案。
  • 数据库驱动:也许我不需要再提这个了。但解决方案应该是数据库驱动的。CMS的目标是面向对象和MVC,所以我需要为字符串考虑一个逻辑数据结构。因为我的模板是结构化的:templates/controller/view.php,也许这个结构最有意义:Controller.View.parameter。数据库表中的这些字段很长,有一个value字段。在模板中,我们可以使用一些排序方法,如echo __('Controller.View.welcome', array('name', 'Joshua')),参数包含Welcome, :name。因此,结果是Welcome, Joshua。这似乎是一个很好的方法,因为编辑器很容易理解诸如:name之类的参数。
  • 数据库负载低:当然,如果在运行中加载这些字符串,上面的系统将导致数据库负载的加载。因此,我需要一个缓存系统,一旦在管理环境中编辑/保存语言文件,它就会重新呈现这些文件。因为文件是生成的,所以还需要一个良好的文件系统布局。我想我们可以和languages/en_EN/Controller/View.php或.ini一起去,任何最适合你的。也许.ini最终的解析速度更快。这个fould应该包含format parameter=value;中的数据。. 我想这是最好的方法,因为渲染的每个视图都可以包含它自己的语言文件(如果存在的话)。然后,应将语言参数加载到特定视图,而不是全局范围内,以防止参数相互覆盖。
  • 数据库表翻译:事实上这是我最担心的事情。我正在寻找一种尽快翻译新闻/页面等的方法。每个模块有两个表(例如NewsNews_translations)是一种选择,但要获得一个好的系统,需要做很多工作。我想到的一个方法是基于我写的data versioning系统:有一个数据库表名Translations,这个表有languagetablenameprimarykey的独特组合。例如:en_en/news/1(指ID=1的新闻项的英文版本)。但是这种方法有两个巨大的缺点:首先,这个表在数据库中有大量数据时往往会变得很长,其次,使用这个设置来搜索这个表将是一项非常困难的工作。例如,搜索该项目的seo slug将是全文搜索,这非常愚蠢。但另一方面:这是一种快速在每一张表中创建可翻译内容的方法,但我不相信这个专业人士会超过骗子。
  • 前端工作:同时前端也需要一些思考。当然,我们会将可用的语言存储在数据库中,(取消)激活我们需要的语言。通过这种方式,脚本可以生成一个下拉列表来选择一种语言,后端可以自动决定什么是tra。


    话题前提

    多语言网站有三个不同的方面:好的。

    • 接口转换
    • 内容
    • URL路由

    虽然它们都以不同的方式相互连接,但从CMS的角度来看,它们使用不同的UI元素进行管理,并且存储方式也不同。您似乎对前两个问题的实现和理解充满信心。问题是关于后一个方面-"URL翻译?我们是否应该这样做?以什么方式?"好的。URL可以由什么组成?

    一件非常重要的事情是,不要对IDN产生幻想。相反,更喜欢音译(也包括:转录和罗马化)。虽然乍一看IDN似乎是国际URL的可行选择,但实际上它并没有像广告中那样工作,原因有两个:好的。

    • 有些浏览器会将非ASCII字符(如'ч''?'转换为'%D1%87''%C5%BE'
    • 如果用户有自定义主题,主题的字体很可能没有这些字母的符号。

    几年前,我在一个基于yii的项目(可怕的框架,imho)中尝试过IDN方法。在找到解决方案之前,我遇到了上述两个问题。另外,我怀疑它可能是一个攻击向量。好的。可用选项…就像我看到他们一样。

    基本上,您有两个选择,可以抽象为:好的。

    • http://site.tld/[:query]:其中[:query]决定语言和内容的选择。好的。

    • http://site.tld/[:language]/[:query]:其中,url的[:language]部分定义了语言的选择,[:query]仅用于标识内容。好的。

    查询是_和Ω..

    假设你选择了http://site.tld/[:query]。好的。

    在这种情况下,您有一个主要的语言来源:[:query]段的内容;以及另外两个来源:好的。

    • 该特定浏览器的值$_COOKIE['lang']
    • HTTP接受语言(1)、(2)头中的语言列表

    首先,您需要将查询与一个定义的路由模式相匹配(如果您的选择是laravel,那么在这里阅读)。成功匹配模式后,您需要找到语言。好的。

    您将不得不遍历模式的所有部分。找到所有这些片段的潜在翻译,并确定使用了哪种语言。另外两个源(cookie和header)将用于解决路由冲突,当它们出现时(而不是"if")。好的。

    例如:http://site.tld/blog/novinka。好的。

    这是"блог, новинка"的音译,英语中的意思大约是"blog","latest"。好的。

    正如你已经注意到的,在俄语中,"бло_"将被音译为"blog"。也就是说,在[:query]的第一部分中,您(在最好的情况下)最终将得到['en', 'ru']的可能语言列表。然后你进入下一段-"Novinka"。可能只有一种语言在可能性列表中:['ru']。好的。

    当列表有一个项目时,您已成功找到语言。好的。

    但如果你最终有2个(例如:俄罗斯和乌克兰)或更多的可能性……或者0种可能性,比如说。您必须使用cookie和/或header来找到正确的选项。好的。

    如果所有其他的都失败了,您可以选择站点的默认语言。好的。语言作为参数

    另一种方法是使用URL,它可以定义为http://site.tld/[:language]/[:query]。在这种情况下,在翻译查询时,不需要猜测语言,因为此时您已经知道要使用哪种语言了。好的。

    还有第二语言来源:cookie值。但是这里没有必要处理accept-language头部,因为在"冷启动"的情况下(当用户第一次使用自定义查询打开站点时),您不会处理未知数量的可能语言。好的。

    相反,您有3个简单的优先选项:好的。

  • 如果设置了[:language]段,则使用它
  • 如果设置了$_COOKIE['lang'],则使用它
  • 使用默认语言
  • 当您使用该语言时,只需尝试转换查询,如果转换失败,则使用该特定段的"默认值"(基于路由结果)。好的。这不是第三种选择吗?

    是的,从技术上讲,您可以将这两种方法结合起来,但这会使过程复杂化,并且只适合那些希望手动将http://site.tld/en/news的url更改为http://site.tld/de/news并希望新闻页面更改为德语的人。好的。

    但即使是这种情况,也可以通过使用cookie值(其中包含有关先前语言选择的信息)来减少实现的魔力和希望。好的。使用哪种方法?

    正如您可能已经猜到的,我建议使用http://site.tld/[:language]/[:query]作为更明智的选择。好的。

    同样,在实际情况下,URL中还有第三个主要部分:"标题"。如在网上商店或新闻网站上的文章标题中的产品名称。好的。

    示例:http://site.tld/en/news/article/121415/EU-as-global-reserve-currency好的。

    在这种情况下,查询将是'/news/article/121415',而'EU-as-global-reserve-currency'是标题。纯粹是为了搜索引擎优化。好的。在拉拉维尔能做到吗?

    有点,但不是默认的。好的。

    我不太熟悉它,但是从我所看到的来看,Laravel使用简单的基于模式的路由机制。要实现多语言URL,您可能需要扩展核心类,因为多语言路由需要访问不同形式的存储(数据库、缓存和/或配置文件)。好的。它被路由了。现在怎么办?

    结果,您将得到两个有价值的信息:当前语言和已翻译的查询段。然后,这些值可用于分派到将生成结果的类。好的。

    基本上,以下URL:http://site.tld/ru/blog/novinka(或没有'/ru'的版本)会变成类似的好的。

    1
    2
    3
    4
    5
    $parameters = [
       'language' => 'ru',
       'classname' => 'blog',
       'method' => 'latest',
    ];

    你只是用来调度的:好的。

    1
    2
    $instance = new {$parameter['classname']};
    $instance->{'get'.$parameters['method']}( $parameters );

    …或者它的一些变体,取决于特定的实现。好的。好啊。


    根据Thomas Bley的建议,使用预处理器实现i18n而不影响性能

    在工作中,我们最近在我们的一些财产上进行了i18n的实现,我们一直在努力的事情之一是处理即时翻译的性能冲击,然后我发现了Thomas Bley的这篇伟大的博客文章,它启发了我们使用i18n以最低的性能处理大流量负载的方式是起诉。好的。

    我们不需要为每个翻译操作调用函数,正如我们在PHP中所知道的那样,我们使用占位符定义基本文件,然后使用预处理器缓存这些文件(我们存储文件修改时间,以确保我们始终提供最新的内容)。好的。翻译标签

    托马斯使用{tr}{/tr}标记来定义翻译的开始和结束位置。由于我们使用的是树枝,所以我们不想使用{来避免混淆,所以我们使用[%tr%][%/tr%]。基本上是这样的:好的。

    1
    `return [%tr%]formatted_value[%/tr%];`

    注意,托马斯建议在文件中使用基础英语。我们不这样做,因为如果我们更改了英文值,就不需要修改所有翻译文件。好的。ini文件

    然后,我们为每种语言创建一个ini文件,格式为placeholder = translated:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    // lang/fr.ini
    formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

    // lang/en_gb.ini
    formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

    // lang/en_us.ini
    formatted_value = '$' . number_format($value)

    允许用户在CMS中修改这些内容是很简单的,只需在
    =上通过preg_split获取密钥对,并使CMS能够写入INI文件。好的。预处理器组件

    从本质上讲,托马斯建议使用一个及时的"编译器"(尽管实际上它是一个预处理器)函数来获取翻译文件并在磁盘上创建静态PHP文件。通过这种方式,我们基本上缓存转换后的文件,而不是为文件中的每个字符串调用转换函数:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // This function was written by Thomas Bley, not by me
    function translate($file) {
      $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
      // (re)build translation?
      if (!file_exists($cache_file)) {
        $lang_file = 'lang/'.LANG.'.ini';
        $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

        // convert .ini file into .php file
        if (!file_exists($lang_file_php)) {
          file_put_contents($lang_file_php, '<?php $strings='.
            var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
        }
        // translate .php into localized .php file
        $tr = function($match) use (&$lang_file_php) {
          static $strings = null;
          if ($strings===null) require($lang_file_php);
          return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
        };
        // replace all {t}abc{/t} by tr()
        file_put_contents($cache_file, preg_replace_callback(
          '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
      }
      return $cache_file;
    }

    注意:我没有验证regex是否有效,我没有从公司服务器复制它,但是您可以看到操作是如何工作的。好的。如何称呼它

    同样,这个例子来自托马斯·布莱,而不是我:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    // instead of
    require("core/example.php");
    echo (new example())->now();

    // we write
    define('LANG', 'en_us');
    require(translate('core/example.php'));
    echo (new example())->now();

    我们将语言存储在一个cookie(或者会话变量,如果我们不能得到cookie的话)中,然后在每个请求中检索它。您可以将此参数与可选的$_GET参数结合起来覆盖该语言,但我不建议每种语言或每种语言的页面都使用子域,因为这样会使您更难看到哪些页面受欢迎,并且会降低入站链接的值,因为您几乎不会传播这些页面。好的。为什么使用这种方法?

    我们喜欢这种预处理方法有三个原因:好的。

  • 不为很少改变的内容调用一系列功能(通过这个系统,10万法语访问者最终只会运行一次翻译替换),从而获得巨大的性能。
  • 它不向我们的数据库添加任何负载,因为它使用简单的平面文件,是纯PHP解决方案。
  • 能够在翻译中使用PHP表达式。
  • 获取已翻译的数据库内容

    我们只是为数据库中名为language的内容添加了一列,然后我们对前面定义的LANG常量使用了一个accessor方法,因此我们的SQL调用(不幸的是,使用zf1)如下所示:好的。

    1
    2
    3
    4
    $query = select()->from($this->_name)
                     ->where('language = ?', User::getLang())
                     ->where('id       = ?', $articleId)
                     ->limit(1);

    我们的文章在idlanguage上有一个复合主键,所以文章54可以存在于所有语言中。我们的LANG如果没有指定,则默认为en_US。好的。URL段塞翻译

    我将在这里结合两件事情,一个是引导程序中的函数,它接受一个用于语言的$_GET参数并覆盖cookie变量,另一个是路由,它接受多个slug。然后您可以在路由中执行类似的操作:好的。

    1
    2
    "/wilkommen" =>"/welcome/lang/de"
    ... etc ...

    这些文件可以存储在一个平面文件中,可以很容易地从管理面板写入。JSON或XML可以提供一个良好的结构来支持它们。好的。其他几个选项的注意事项

    基于fly翻译的PHP好的。

    我看不出这些比预处理的翻译有任何优势。好的。

    基于前端的翻译好的。

    我早就发现这些很有趣,但有几个注意事项。例如,您必须向用户提供您计划翻译的网站上的完整短语列表,如果您隐藏或不允许他们访问网站的某些区域,这可能会有问题。好的。

    您还必须假设您的所有用户都愿意并且能够在您的站点上使用javascript,但是根据我的统计,大约2.5%的用户没有使用它(或者使用noscript阻止我们的站点使用它)。好的。

    数据库驱动的翻译好的。

    PHP的数据库连接速度没有什么好写的,这增加了对每个要翻译的短语调用函数的开销。这种方法的性能和可伸缩性问题似乎难以解决。好的。好啊。


    我建议你不要发明一个轮子,使用gettext和iso语言缩写列表。您是否看到i18n/l10n是如何在流行的CMSE或框架中实现的?

    使用getText,您将拥有一个强大的工具,其中许多情况已经实现,比如复数形式的数字。在英语中,你只有两种选择:单数和复数。但以俄语为例,它有三种形式,而不像英语那么简单。

    还有许多翻译人员已经有了使用gettext的经验。

    看看Cakephp或Drupal。两种语言都已启用。cakephp作为接口本地化的例子,drupal作为内容翻译的例子。

    对于l10n来说,使用数据库一点都不合适。如果有疑问,那将是一大堆。标准的方法是在早期阶段(或者在第一次调用i10n函数时,如果您喜欢延迟加载的话)将所有l10n数据获取到内存中。它可以同时从.po文件或db中读取所有数据。而不仅仅是从数组中读取请求的字符串。

    如果您需要实现在线工具来转换接口,那么您可以将所有数据保存在数据库中,但仍然需要将所有数据保存到文件中以便使用它。为了减少内存中的数据量,您可以将所有已翻译的消息/字符串拆分为多个组,如果可能的话,只加载所需的组。

    所以你完全正确。有一个例外:通常是一个大文件,而不是每个控制器的文件。因为打开一个文件对性能最好。您可能知道一些高负载的Web应用程序将所有PHP代码编译在一个文件中,以避免在调用include/require时执行文件操作。

    关于URL。谷歌间接建议使用翻译:

    to clearly indicate French content:
    http://example.ca/fr/vélo-de-montagne.html

    另外,我认为您需要将用户重定向到默认语言前缀,例如http://examlpe.com/about-us将重定向到http://examlpe.com/en/about-us。但是如果你的网站只使用一种语言,那么你根本不需要前缀。

    退房:http://www.audiomico.com/trailer-hit-impact-psychoatracy-sound-effects-836925http://nl.audiomico.com/aanhangwagen-hit-effect-psychoatracy-geluideseffecten-836925http://de.audiomico.com/anhanger-hit-auswirkungen-psychoatracy-sound-effekte-836925

    翻译内容更为困难。我认为不同类型的内容会有所不同,例如文章、菜单项等,但在4中,你的方法是正确的。看看Drupal有更多的想法。它有足够清晰的数据库模式和足够好的翻译接口。就像你创建文章并为它选择语言一样。然后你可以把它翻译成其他语言。

    Drupal translation interface

    我认为这与URL块没有问题。您可以为slugs创建单独的表,这将是正确的决定。另外,使用正确的索引,即使有大量的数据,查询表也没有问题。它不是全文搜索,而是字符串匹配,如果将对slug使用varchar数据类型,您也可以在该字段上有一个索引。

    抱歉,我的英语还远远不够好。


    这取决于你的网站有多少内容。起初,我像这里的其他人一样使用一个数据库,但编写数据库的所有工作脚本可能很费时。我不认为这是一个理想的方法,尤其是如果你有大量的文本,但是如果你想在不使用数据库的情况下快速完成,这个方法可以工作,但是,你不能允许用户输入数据,这些数据将被用作翻译文件。但是,如果您自己添加翻译,它将起作用:

    假设您有以下文本:

    1
    Welcome!

    您可以在数据库中输入翻译,但也可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $welcome = array(
    "English"=>"Welcome!",
    "German"=>"Willkommen!",
    "French"=>"Bienvenue!",
    "Turkish"=>"Ho?geldiniz!",
    "Russian"=>"Добро пожаловать!",
    "Dutch"=>"Welkom!",
    "Swedish"=>"V?lkommen!",
    "Basque"=>"Ongietorri!",
    "Spanish"=>"Bienvenito!"
    "Welsh"=>"Croeso!");

    现在,如果您的网站使用cookie,您可以使用它,例如:

    1
    $_COOKIE['language'];

    为了方便起见,让我们将其转换为易于使用的代码:

    1
    $language=$_COOKIE['language'];

    如果你的曲奇语言是威尔士语,你有这段代码:

    4

    结果是:

    1
    Croeso!

    如果您需要为您的网站添加大量翻译,并且数据库过于消耗,那么使用数组可能是一个理想的解决方案。


    我建议您不要真正依赖数据库进行翻译,这可能是一项非常棘手的任务,在数据编码的情况下可能是一个极端的问题。

    我以前也遇到过类似的问题,为了解决我的问题,我在下课写了一篇文章。

    对象:区域设置区域设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    <?php

      namespace Locale;

      class Locale{

    // Following array stolen from Zend Framework
    public $country_to_locale = array(
        'AD' => 'ca_AD',
        'AE' => 'ar_AE',
        'AF' => 'fa_AF',
        'AG' => 'en_AG',
        'AI' => 'en_AI',
        'AL' => 'sq_AL',
        'AM' => 'hy_AM',
        'AN' => 'pap_AN',
        'AO' => 'pt_AO',
        'AQ' => 'und_AQ',
        'AR' => 'es_AR',
        'AS' => 'sm_AS',
        'AT' => 'de_AT',
        'AU' => 'en_AU',
        'AW' => 'nl_AW',
        'AX' => 'sv_AX',
        'AZ' => 'az_Latn_AZ',
        'BA' => 'bs_BA',
        'BB' => 'en_BB',
        'BD' => 'bn_BD',
        'BE' => 'nl_BE',
        'BF' => 'mos_BF',
        'BG' => 'bg_BG',
        'BH' => 'ar_BH',
        'BI' => 'rn_BI',
        'BJ' => 'fr_BJ',
        'BL' => 'fr_BL',
        'BM' => 'en_BM',
        'BN' => 'ms_BN',
        'BO' => 'es_BO',
        'BR' => 'pt_BR',
        'BS' => 'en_BS',
        'BT' => 'dz_BT',
        'BV' => 'und_BV',
        'BW' => 'en_BW',
        'BY' => 'be_BY',
        'BZ' => 'en_BZ',
        'CA' => 'en_CA',
        'CC' => 'ms_CC',
        'CD' => 'sw_CD',
        'CF' => 'fr_CF',
        'CG' => 'fr_CG',
        'CH' => 'de_CH',
        'CI' => 'fr_CI',
        'CK' => 'en_CK',
        'CL' => 'es_CL',
        'CM' => 'fr_CM',
        'CN' => 'zh_Hans_CN',
        'CO' => 'es_CO',
        'CR' => 'es_CR',
        'CU' => 'es_CU',
        'CV' => 'kea_CV',
        'CX' => 'en_CX',
        'CY' => 'el_CY',
        'CZ' => 'cs_CZ',
        'DE' => 'de_DE',
        'DJ' => 'aa_DJ',
        'DK' => 'da_DK',
        'DM' => 'en_DM',
        'DO' => 'es_DO',
        'DZ' => 'ar_DZ',
        'EC' => 'es_EC',
        'EE' => 'et_EE',
        'EG' => 'ar_EG',
        'EH' => 'ar_EH',
        'ER' => 'ti_ER',
        'ES' => 'es_ES',
        'ET' => 'en_ET',
        'FI' => 'fi_FI',
        'FJ' => 'hi_FJ',
        'FK' => 'en_FK',
        'FM' => 'chk_FM',
        'FO' => 'fo_FO',
        'FR' => 'fr_FR',
        'GA' => 'fr_GA',
        'GB' => 'en_GB',
        'GD' => 'en_GD',
        'GE' => 'ka_GE',
        'GF' => 'fr_GF',
        'GG' => 'en_GG',
        'GH' => 'ak_GH',
        'GI' => 'en_GI',
        'GL' => 'iu_GL',
        'GM' => 'en_GM',
        'GN' => 'fr_GN',
        'GP' => 'fr_GP',
        'GQ' => 'fan_GQ',
        'GR' => 'el_GR',
        'GS' => 'und_GS',
        'GT' => 'es_GT',
        'GU' => 'en_GU',
        'GW' => 'pt_GW',
        'GY' => 'en_GY',
        'HK' => 'zh_Hant_HK',
        'HM' => 'und_HM',
        'HN' => 'es_HN',
        'HR' => 'hr_HR',
        'HT' => 'ht_HT',
        'HU' => 'hu_HU',
        'ID' => 'id_ID',
        'IE' => 'en_IE',
        'IL' => 'he_IL',
        'IM' => 'en_IM',
        'IN' => 'hi_IN',
        'IO' => 'und_IO',
        'IQ' => 'ar_IQ',
        'IR' => 'fa_IR',
        'IS' => 'is_IS',
        'IT' => 'it_IT',
        'JE' => 'en_JE',
        'JM' => 'en_JM',
        'JO' => 'ar_JO',
        'JP' => 'ja_JP',
        'KE' => 'en_KE',
        'KG' => 'ky_Cyrl_KG',
        'KH' => 'km_KH',
        'KI' => 'en_KI',
        'KM' => 'ar_KM',
        'KN' => 'en_KN',
        'KP' => 'ko_KP',
        'KR' => 'ko_KR',
        'KW' => 'ar_KW',
        'KY' => 'en_KY',
        'KZ' => 'ru_KZ',
        'LA' => 'lo_LA',
        'LB' => 'ar_LB',
        'LC' => 'en_LC',
        'LI' => 'de_LI',
        'LK' => 'si_LK',
        'LR' => 'en_LR',
        'LS' => 'st_LS',
        'LT' => 'lt_LT',
        'LU' => 'fr_LU',
        'LV' => 'lv_LV',
        'LY' => 'ar_LY',
        'MA' => 'ar_MA',
        'MC' => 'fr_MC',
        'MD' => 'ro_MD',
        'ME' => 'sr_Latn_ME',
        'MF' => 'fr_MF',
        'MG' => 'mg_MG',
        'MH' => 'mh_MH',
        'MK' => 'mk_MK',
        'ML' => 'bm_ML',
        'MM' => 'my_MM',
        'MN' => 'mn_Cyrl_MN',
        'MO' => 'zh_Hant_MO',
        'MP' => 'en_MP',
        'MQ' => 'fr_MQ',
        'MR' => 'ar_MR',
        'MS' => 'en_MS',
        'MT' => 'mt_MT',
        'MU' => 'mfe_MU',
        'MV' => 'dv_MV',
        'MW' => 'ny_MW',
        'MX' => 'es_MX',
        'MY' => 'ms_MY',
        'MZ' => 'pt_MZ',
        'NA' => 'kj_NA',
        'NC' => 'fr_NC',
        'NE' => 'ha_Latn_NE',
        'NF' => 'en_NF',
        'NG' => 'en_NG',
        'NI' => 'es_NI',
        'NL' => 'nl_NL',
        'NO' => 'nb_NO',
        'NP' => 'ne_NP',
        'NR' => 'en_NR',
        'NU' => 'niu_NU',
        'NZ' => 'en_NZ',
        'OM' => 'ar_OM',
        'PA' => 'es_PA',
        'PE' => 'es_PE',
        'PF' => 'fr_PF',
        'PG' => 'tpi_PG',
        'PH' => 'fil_PH',
        'PK' => 'ur_PK',
        'PL' => 'pl_PL',
        'PM' => 'fr_PM',
        'PN' => 'en_PN',
        'PR' => 'es_PR',
        'PS' => 'ar_PS',
        'PT' => 'pt_PT',
        'PW' => 'pau_PW',
        'PY' => 'gn_PY',
        'QA' => 'ar_QA',
        'RE' => 'fr_RE',
        'RO' => 'ro_RO',
        'RS' => 'sr_Cyrl_RS',
        'RU' => 'ru_RU',
        'RW' => 'rw_RW',
        'SA' => 'ar_SA',
        'SB' => 'en_SB',
        'SC' => 'crs_SC',
        'SD' => 'ar_SD',
        'SE' => 'sv_SE',
        'SG' => 'en_SG',
        'SH' => 'en_SH',
        'SI' => 'sl_SI',
        'SJ' => 'nb_SJ',
        'SK' => 'sk_SK',
        'SL' => 'kri_SL',
        'SM' => 'it_SM',
        'SN' => 'fr_SN',
        'SO' => 'sw_SO',
        'SR' => 'srn_SR',
        'ST' => 'pt_ST',
        'SV' => 'es_SV',
        'SY' => 'ar_SY',
        'SZ' => 'en_SZ',
        'TC' => 'en_TC',
        'TD' => 'fr_TD',
        'TF' => 'und_TF',
        'TG' => 'fr_TG',
        'TH' => 'th_TH',
        'TJ' => 'tg_Cyrl_TJ',
        'TK' => 'tkl_TK',
        'TL' => 'pt_TL',
        'TM' => 'tk_TM',
        'TN' => 'ar_TN',
        'TO' => 'to_TO',
        'TR' => 'tr_TR',
        'TT' => 'en_TT',
        'TV' => 'tvl_TV',
        'TW' => 'zh_Hant_TW',
        'TZ' => 'sw_TZ',
        'UA' => 'uk_UA',
        'UG' => 'sw_UG',
        'UM' => 'en_UM',
        'US' => 'en_US',
        'UY' => 'es_UY',
        'UZ' => 'uz_Cyrl_UZ',
        'VA' => 'it_VA',
        'VC' => 'en_VC',
        'VE' => 'es_VE',
        'VG' => 'en_VG',
        'VI' => 'en_VI',
        'VN' => 'vn_VN',
        'VU' => 'bi_VU',
        'WF' => 'wls_WF',
        'WS' => 'sm_WS',
        'YE' => 'ar_YE',
        'YT' => 'swb_YT',
        'ZA' => 'en_ZA',
        'ZM' => 'en_ZM',
        'ZW' => 'sn_ZW'
    );

    /**
     * Store the transaltion for specific languages
     *
     * @var array
     */

    protected $translation = array();

    /**
     * Current locale
     *
     * @var string
     */

    protected $locale;

    /**
     * Default locale
     *
     * @var string
     */

    protected $default_locale;

    /**
     *
     * @var string
     */

    protected $locale_dir;

    /**
     * Construct.
     *
     *
     * @param string $locale_dir            
     */

    public function __construct($locale_dir)
    {
        $this->locale_dir = $locale_dir;
    }

    /**
     * Set the user define localte
     *
     * @param string $locale            
     */

    public function setLocale($locale = null)
    {
        $this->locale = $locale;

        return $this;
    }

    /**
     * Get the user define locale
     *
     * @return string
     */

    public function getLocale()
    {
        return $this->locale;
    }

    /**
     * Get the Default locale
     *
     * @return string
     */

    public function getDefaultLocale()
    {
        return $this->default_locale;
    }

    /**
     * Set the default locale
     *
     * @param string $locale            
     */

    public function setDefaultLocale($locale)
    {
        $this->default_locale = $locale;

        return $this;
    }

    /**
     * Determine if transltion exist or translation key exist
     *
     * @param string $locale            
     * @param string $key            
     * @return boolean
     */

    public function hasTranslation($locale, $key = null)
    {
        if (null == $key && isset($this->translation[$locale])) {
            return true;
        } elseif (isset($this->translation[$locale][$key])) {
            return true;
        }

        return false;
    }

    /**
     * Get the transltion for required locale or transtion for key
     *
     * @param string $locale            
     * @param string $key            
     * @return array
     */

    public function getTranslation($locale, $key = null)
    {
        if (null == $key && $this->hasTranslation($locale)) {
            return $this->translation[$locale];
        } elseif ($this->hasTranslation($locale, $key)) {
            return $this->translation[$locale][$key];
        }

        return array();
    }

    /**
     * Set the transtion for required locale
     *
     * @param string $locale
     *            Language code
     * @param string $trans
     *            translations array
     */

    public function setTranslation($locale, $trans = array())
    {
        $this->translation[$locale] = $trans;
    }

    /**
     * Remove transltions for required locale
     *
     * @param string $locale            
     */

    public function removeTranslation($locale = null)
    {
        if (null === $locale) {
            unset($this->translation);
        } else {
            unset($this->translation[$locale]);
        }
    }

    /**
     * Initialize locale
     *
     * @param string $locale            
     */

    public function init($locale = null, $default_locale = null)
    {
        // check if previously set locale exist or not
        $this->init_locale();
        if ($this->locale != null) {
            return;
        }

        if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
            $this->detectLocale();
        } else {
            $this->locale = $locale;
        }

        $this->init_locale();
    }

    /**
     * Attempt to autodetect locale
     *
     * @return void
     */

    private function detectLocale()
    {
        $locale = false;

        // GeoIP
        if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

            $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

            if ($country) {

                $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
            }
        }

        // Try detecting locale from browser headers
        if (! $locale) {

            if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

                $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

                foreach ($languages as $lang) {

                    $lang = str_replace('-', '_', trim($lang));

                    if (strpos($lang, '_') === false) {

                        if (isset($this->country_to_locale[strtoupper($lang)])) {

                            $locale = $this->country_to_locale[strtoupper($lang)];
                        }
                    } else {

                        $lang = explode('_', $lang);

                        if (count($lang) == 3) {
                            // language_Encoding_COUNTRY
                            $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                        } else {
                            // language_COUNTRY
                            $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                        }

                        return;
                    }
                }
            }
        }

        // Resort to default locale specified in config file
        if (! $locale) {
            $this->locale = $this->default_locale;
        }
    }

    /**
     * Check if config for selected locale exists
     *
     * @return void
     */

    private function init_locale()
    {
        if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
            $this->locale = $this->default_locale;
        }
    }

    /**
     * Load a Transtion into array
     *
     * @return void
     */

    private function loadTranslation($locale = null, $force = false)
    {
        if ($locale == null)
            $locale = $this->locale;

        if (! $this->hasTranslation($locale)) {
            $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
        }
    }

    /**
     * Translate a key
     *
     * @param
     *            string Key to be translated
     * @param
     *            string optional arguments
     * @return string
     */

    public function translate($key)
    {
        $this->init();
        $this->loadTranslation($this->locale);

        if (! $this->hasTranslation($this->locale, $key)) {

            if ($this->locale !== $this->default_locale) {

                $this->loadTranslation($this->default_locale);

                if ($this->hasTranslation($this->default_locale, $key)) {

                    $translation = $this->getTranslation($this->default_locale, $key);
                } else {
                    // return key as it is or log error here
                    return $key;
                }
            } else {
                return $key;
            }
        } else {
            $translation = $this->getTranslation($this->locale, $key);
        }
        // Replace arguments
        if (false !== strpos($translation, '{a:')) {
            $replace = array();
            $args = func_get_args();
            for ($i = 1, $max = count($args); $i < $max; $i ++) {
                $replace['{a:' . $i . '}'] = $args[$i];
            }
            // interpolate replacement values into the messsage then return
            return strtr($translation, $replace);
        }

        return $translation;
      }
    }

    用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     <?php
        ## /locale/en.php

        return array(
           'name' => 'Hello {a:1}'
           'name_full' => 'Hello {a:1} {a:2}'
       );

    $locale = new Locale(__DIR__ . '/locale');
    $locale->setLocale('en');// load en.php from locale dir
    //want to work with auto detection comment $locale->setLocale('en');

    echo $locale->translate('name', 'Foo');
    echo $locale->translate('name', 'Foo', 'Bar');

    它是如何工作的

    {a:1}替换为传递给方法Locale::translate('key_name','arg1')的第一个参数。{a:2}替换为传递给方法Locale::translate('key_name','arg1','arg2')的第二个参数。

    检测工作原理

    • 默认情况下,如果安装了geoip,那么它将通过geoip_country_code_by_name返回国家代码;如果没有安装geoip,则回退到HTTP_ACCEPT_LANGUAGE头段。


    只是一个次要答案:绝对要使用前面有语言标识符的翻译后的URL:http://www.domain.com/nl/over-ons混合解决方案往往会变得复杂,所以我会坚持下去。为什么?因为URL对于SEO是必不可少的。

    关于数据库翻译:语言的数量多少是固定的?或者更不可预测和动态?如果它是固定的,我只需要添加新的列,否则将使用多个表。

    但一般来说,为什么不使用Drupal呢?我知道每个人都想建立自己的CMS,因为它更快,更瘦,等等,但这真是一个坏主意!


    我不打算试图完善已经给出的答案。相反,我将告诉您我自己的oop-php框架处理翻译的方式。好的。

    在内部,我的框架使用诸如en、fr、es、cn等代码。数组包含网站支持的语言:array('en'、'fr'、'es'、'cn')语言代码通过$u get(lang=fr)传递,如果未传递或无效,则将其设置为数组中的第一种语言。所以在程序执行期间的任何时候,从一开始,当前语言就是已知的。好的。

    理解在典型应用程序中需要翻译的内容是很有用的:好的。

    1)来自类(或过程代码)的错误消息2)来自类(或过程代码)的非错误消息3)页面内容(通常存储在数据库中)4)站点范围字符串(如网站名称)5)脚本特定字符串好的。

    第一种类型很容易理解。基本上,我们谈论的是"无法连接到数据库…"之类的消息。只有在出现错误时才需要加载这些消息。我的管理器类接收来自其他类的调用,使用作为参数传递的信息,只需转到相关的类文件夹并检索错误文件。好的。

    第二类错误消息更像是表单验证出错时得到的消息。(你不能离开…空白"或"请选择超过5个字符的密码")。在类运行之前需要加载字符串。我知道什么是好的。

    对于实际的页面内容,我为每种语言使用一个表,每个表的前缀都是该语言的代码。因此,en-u内容是英语内容的表格,es-u内容是西班牙的表格,cn-u内容是中国的表格,fr-u内容是法国的表格。好的。

    第四种字符串在整个网站中都是相关的。这是通过一个使用该语言的代码命名的配置文件加载的,即en-lang.php、es-lang.php等。在全局语言文件中,您需要在英语全局文件中加载array("英语"、"中文"、"西班牙语"、"法语")等翻译语言,并在法语文件中加载array("英语"、"chinois"、"espagnol"、"francais")。因此,当您为语言选择填充下拉列表时,它使用的语言是正确的;)好的。

    最后是脚本特定的字符串。所以,如果你写一个烹饪应用程序,它可能是"你的烤箱不够热"。好的。

    在我的应用程序周期中,首先加载全局语言文件。在这里,您不仅可以找到全局字符串(如"杰克的网站"),还可以找到一些类的设置。基本上,任何与语言或文化相关的东西。其中的一些字符串包括日期掩码(mmddyyyy或ddmmyyyy)或ISO语言代码。在主语言文件中,我包含了各个类的字符串,因为它们太少了。好的。

    从磁盘读取的第二个也是最后一个语言文件是脚本语言文件。lang_en_home_welcome.php是home/welcome脚本的语言文件。脚本由模式(home)和操作(welcome)定义。每个脚本都有自己的文件夹,其中包含config和lang文件。好的。

    该脚本从数据库中提取命名内容表的内容,如上文所述。好的。

    如果出现问题,管理器知道从何处获取与语言相关的错误文件。只有在出现错误时才加载该文件。好的。

    所以结论是显而易见的。在开始开发应用程序或框架之前,请考虑翻译问题。您还需要一个包含翻译的开发工作流。使用我的框架,我用英语开发整个站点,然后翻译所有相关的文件。好的。

    在实现翻译字符串的过程中,只需要一个快速的最后一个词。我的框架只有一个全局的$manager,它运行任何其他服务可用的服务。因此,例如表单服务获取HTML服务并使用它来编写HTML。我系统中的一个服务是翻译服务。$translator->set($service,$code,$string)为当前语言设置一个字符串。语言文件是此类语句的列表。$translator->get($service,$code)检索翻译字符串。$code可以是类似1的数字,也可以是类似于'no_connection'的字符串。服务之间不能有冲突,因为每个服务在转换器的数据区域中都有自己的命名空间。好的。

    我把这个贴在这里,希望它能像我多年前做的那样,为别人省去重新发明轮子的任务。好的。好啊。


    在开始使用symfony框架之前,我有过同样的问题。

  • 只需使用一个函数uu(),它具有参数pageid(或objectid,objecttable,如2所述)、目标语言和回退(默认)语言的可选参数。可以在一些全局配置中设置默认语言,以便以后更容易地更改它。

  • 为了在数据库中存储内容,我使用了以下结构:(pageid、语言、内容、变量)。

    • pageid将是一个FK到您想要翻译的页面。如果有其他对象,如新闻、库或其他对象,只需将其拆分为两个字段:objectid、objecttable。

    • 语言-显然,它将存储ISO语言字符串en-en、lt-lt、en-us等。

    • 内容-要与通配符一起转换以替换变量的文本。示例"你好,先生,%%name%"。您的帐户余额是%%balance%%."

    • 变量-JSON编码的变量。PHP提供了快速解析这些内容的函数。示例"name:laurynas,balance:15.23"。

    • 你还提到了史拉格菲尔德。您可以自由地将其添加到此表中,以便快速搜索它。

  • 必须通过缓存翻译将数据库调用减少到最小。它必须存储在PHP数组中,因为它是PHP语言中最快的结构。如何创建这个缓存取决于您自己。根据我的经验,对于每种支持的语言,您应该有一个文件夹,对于每种pageid应该有一个数组。应该在更新转换后重建缓存。只应重新生成已更改的数组。

  • 我想我在2中回答了这个问题

  • 你的想法完全合乎逻辑。这个很简单,我想不会给你带来任何问题。

  • URL应该使用翻译表中存储的段塞进行翻译。

    最后的话

    研究最佳实践总是好的,但不要重新发明轮子。只需从已知框架中获取并使用组件,然后使用它们。

    看看symfony翻译组件。它可能是一个很好的代码库。


    我一遍又一遍地问自己相关的问题,然后就迷上了正规语言……但为了帮你一点忙,我想分享一些发现:好的。

    我建议看一下高级CMS好的。

    Typo3代表PHP(我知道有很多东西,但我认为这是最成熟的东西)好的。

    Python中的Plone。好的。

    如果你发现2013年的网络应该有所不同,那就从头开始吧。这将意味着组建一个由高技能/经验丰富的人员组成的团队来构建一个新的CMS。也许你想看看聚合物的用途。好的。

    如果涉及到编码和多语言网站/本机语言支持,我认为每个程序员都应该了解Unicode。如果你不知道Unicode,你肯定会把你的数据弄乱。不要使用成千上万的ISO代码。他们只会给你留点记忆。但是你可以用UTF-8做任何事情,甚至存储中文字符。但为了实现这一点,您需要存储2或4字节字符,这使得它基本上是一个utf-16或utf-32。好的。

    如果是关于URL编码的,那么你也不应该混合编码,并且要知道至少对于域名来说,有一些规则是由不同的大厅定义的,它们提供类似浏览器的应用程序。例如,一个域可能非常类似:好的。

    leng ankofamerica.com或bankofamerica.com samesamebutdifferent;)好的。

    当然,您需要文件系统来处理所有编码。使用UTF-8文件系统的Unicode的另一个优点。好的。

    如果是关于翻译的,考虑一下文档的结构。例如一本书或一篇文章。您有docbook规范来了解这些结构。但在HTML中,它只是关于内容块。所以你想在那个级别上有一个翻译,也在网页级别或域级别。因此,如果一个块不存在,它就不存在,如果一个网页不存在,你将被重定向到较高的导航级别。如果一个域在导航结构上完全不同,那么..它是一个完全不同的管理结构。这可以用typo3来完成。好的。

    如果是关于框架的,我所知道的最成熟的框架,去做像MVC这样的一般事情(我真的很讨厌这个词!像"性能"一样,如果你想卖东西,用"性能和特性"这个词,你就卖……他妈的)是Zend。事实证明,将标准带给PHP混乱编码人员是一件好事。但是,typo3除了CMS还有一个框架。最近它被重新开发,现在被称为流程3。当然,这些框架包括数据库抽象、模板化和缓存概念,但是它们有各自的优势。好的。

    如果是关于缓存…这可能非常复杂/多层。在PHP中,您会想到Accelerator、Opcode,还有HTML、HTTPD、MySQL、XML、CSS、JS……任何类型的缓存。当然,有些部分应该缓存,而动态部分(如博客答案)则不应该缓存。有些应该通过Ajax通过生成的URL请求。JSON、HashBangs等。好的。

    然后,您希望网站上的任何小组件只能由特定的用户访问或管理,因此从概念上讲,这起到了很大的作用。好的。

    此外,你还想做统计,可能有分布式系统/Facebook的facebook等。任何软件将建立在你的超顶级CMS之上…因此,您需要在内存、bigdata、xml中使用不同类型的数据库。好的。

    好吧,我想这就够了。如果您没有听说typo3/plone或提到的框架,那么您就有足够的学习空间。在这条路上,你会找到很多解决你还没有问到的问题的方法。好的。

    如果你认为,让我们做一个新的CMS,因为它的2013年和PHP即将死去,那么你R欢迎加入任何其他的开发团队,希望不会迷路。好的。

    祝你好运!好的。

    顺便说一句,未来人们不会再有任何网站了?我们都会在Google+上?我希望开发人员变得更有创造力,做一些有用的事情(不要被博尔格人同化)好的。

    /////////只需考虑一下您现有的应用程序:好的。

    如果您有一个php-mysql-cms,并且想要嵌入多语言支持。您可以将表与任何语言的附加列一起使用,也可以在同一个表中插入具有对象ID和语言ID的翻译,或者为任何语言创建相同的表并在其中插入对象,然后在希望所有对象都显示时进行选择联合。对于数据库,使用utf8常规CI,当然在前端/后端使用utf8文本/编码。我已经按照你已经解释过的方式使用了URL路径段好的。

    域.org/en/about您可以将语言ID映射到内容表。无论如何,您需要有一个URL的参数映射,这样您就可以定义一个从URL中的路径段映射的参数,例如。好的。

    域.org/en/about/employees/it/administrators/好的。

    查找配置好的。

    页面地址好的。

    1/关于/员工/../..好的。

    1/…/关于/员工…..。/好的。

    将参数映射到URL路径段"好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
    $parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
    $parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result

    $websiteconfig[]=$userwhatever;
    $websiteconfig[]=$parameterlist;
    $someparameterlist[] = array("branch"=>$someid);
    $someparameterlist[] = array("employertype"=>$someid);
    function getURL($someparameterlist){
    // todo foreach someparameter lookup pathsegment
    return path;
    }

    比如说,上面的帖子已经介绍过了。好的。

    不要忘记,您需要"重写"生成php文件的URL,在大多数情况下,该文件就是index.php。好的。好啊。


    数据库工作:

    创建语言表"语言":

    领域:

    语言ID(主要和自动增加)

    语言名

    创造在

    被创造的

    更新的AT

    通过更新

    在数据库"content"中创建表:

    领域:

    内容ID(主要和自动增加)

    主要内容

    标题内容

    足叶内容物

    左侧边栏内容

    右侧边栏内容

    语言u id(外键:引用到语言表)

    创造在

    被创造的

    更新的AT

    通过更新

    前端工作:

    当用户从下拉列表或任何区域中选择任何语言时,将所选语言ID保存在会话中,如下所示:

    $会话[‘语言’]=1;

    现在根据会话中存储的语言ID从数据库表"content"中提取数据。

    详情可在以下网址找到:http://skillrow.com/multilingual-website-in-php-2/


    作为一个住在魁北克的人,几乎所有的网站都是法语和英语…我已经尝试了很多,如果不是大多数的多语言插件为wp…唯一有用的解决方案是mqtranslate…我和它一起生活和死亡!

    https://wordpress.org/plugins/mqtranslate/


    WordPress+EDOCX1(插件)怎么样?现场将具有以下结构:

    • 示例.com/eng/category1/…
    • 示例.com/eng/my page….
    • 示例.com/rus/category1/…
    • 示例.com/rus/my page….

    该插件提供了翻译所有短语的接口,具有简单的逻辑:

    1
    2
    (ENG) my_title -"Hello user"
    (SPA) my_title -"Holla usuario"

    然后可以输出:
    echo translate('my_title', LNG); // LNG is auto-detected

    但是,请检查插件是否仍处于活动状态。


    一个非常简单的选项是www.multilingualizer.com,它适用于任何可以上传javascript的网站。

    它允许您将所有语言的所有文本放到一个页面上,然后隐藏用户不需要看到的语言。很好用。