最佳实践多语言网站


178

我已经在这个问题上苦苦挣扎了好几个月了,但是以前我从来没有需要探索所有可能的选择的情况。现在,我觉得该是时候了解各种可能性并建立自己的个人喜好了,以便在我即将进行的项目中使用。

首先让我勾勒出我要寻找的情况

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

应该翻译什么

由于我正在寻找的系统需要尽可能地方便用户使用,因此管理翻译的方法应在CMS内部。无需启动FTP连接即可修改翻译文件或任何html / php解析的模板。

此外,我正在寻找最简单的方法来转换多个数据库表,而无需创建其他表。

我自己想到了什么

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

  1. PHP解析模板:模板系统应由PHP解析。这样,我就可以将转换后的参数插入HTML,而不必打开模板并进行修改。除此之外,PHP解析的模板使我能够为整个网站使用1个模板,而不必为每种语言都拥有一个子文件夹(这是我以前使用的)。达到此目标的方法可以是Smarty,TemplatePower,Laravel's Blade或任何其他模板解析器。正如我所说,这应该独立于书面解决方案。
  2. 数据库驱动的:也许我不需要再次提及。但是解决方案应该是数据库驱动的。CMS的目标是面向对象和MVC,所以我需要考虑字符串的逻辑数据结构。正如我的模板将被结构化:模板/控制器/ View.php也许这一结构将最有意义:Controller.View.parameter。数据库表将这些字段与字段长在一起value。在模板内部,我们可以使用某种排序方法,例如echo __('Controller.View.welcome', array('name', 'Joshua'))和参数contains Welcome, :name。因此结果是Welcome, Joshua。这似乎是执行此操作的好方法,因为编辑器很容易理解诸如:name之类的参数。
  3. 数据库负载低:如果在旅途中加载这些字符串,上述系统当然会导致数据库负载。因此,我需要一个缓存系统,以便在管理环境中编辑/保存语言文件后立即重新呈现它们。由于生成了文件,因此还需要一个良好的文件系统布局。我想我们可以选择languages/en_EN/Controller/View.php最适合的是.ini或.ini。也许.ini甚至可以更快地解析。该数据应包含中的数据format parameter=value; 。我猜这是最好的方法,因为渲染的每个View都可以包含它自己的语言文件(如果存在)。然后,应将语言参数加载到特定视图,而不是在全局范围内加载,以防止参数相互覆盖。
  4. 数据库表翻译:实际上,这是我最担心的事情。我正在寻找一种创建News / Pages / etc的翻译的方法。尽快。每个模块都有两个表(例如NewsNews_translations)是一个选项,但是要获得一个好的系统感觉很费力。有一个问题我想出了是基于对事物data versioning系统我写的:有一个数据库表名Translations,这个表有一个独特的组合languagetablenameprimarykey。例如:en_En / News / 1(请参阅ID为1的“新闻”项目的英文版本)。但是此方法有两个巨大的缺点:首先,该表往往会在数据库中存储大量数据时变得很长,其次,使用此设置来搜索表将是一件艰巨的工作。例如,搜索该项目的SEO段将是全文搜索,这真是愚蠢。但另一方面:这是一种非常快速地在每个表中创建可翻译内容的快速方法,但我不认为这会增加缺点。
  5. 前端工作:前端也需要一些思考。当然,我们会将可用的语言存储在数据库中,并停用所需的语言。这样,脚本可以生成一个下拉菜单来选择一种语言,后端可以自动决定可以使用CMS进行哪些翻译。然后,在获取用于视图的语言文件或为网站上的内容项获取正确的翻译时,将使用所选的语言(例如en_EN)。

因此,它们在那里。到目前为止,我的想法。它们甚至还不包括日期等的本地化选项,但是由于我的服务器支持PHP5.3.2 +,因此最好的选择是使用国际扩展名,如此处所述:http ://devzone.zend.com/1500/internationalization-in -php-53 / -但这将在以后的开发中使用。目前,主要问题是如何拥有网站内容翻译的最佳实践。

除了我在这里解释的所有内容之外,我还有另一件事尚未决定,它看起来像一个简单的问题,但实际上,这让我头疼:

网址翻译?我们应该这样做吗?又以什么方式?

所以..如果我有这个网址:http://www.domain.com/about-us英语是我的默认语言。http://www.domain.com/over-ons当我选择荷兰语作为我的语言时,该网址应该翻译成吗?还是我们应该走简单的道路,而只需更改可见的页面内容/about。最后一件事似乎不是一个有效的选择,因为这将生成同一URL的多个版本,对内容进行索引将无法正确进行。

另一个选择是使用http://www.domain.com/nl/about-us。这将为每个内容至少生成一个唯一的URL。同样,这将更易于使用另一种语言,例如http://www.domain.com/en/about-us,所提供的URL对于Google和人类访问者而言都更易于理解。使用此选项,我们如何处理默认语言?默认语言是否应删除默认选择的语言?所以重定向http://www.domain.com/en/about-ushttp://www.domain.com/about-us...在我看来,这是最好的解决方案,因为当仅针对一种语言设置CMS时,无需在URL中使用此语言标识。

第三个选项是这两个选项的组合:http://www.domain.com/about-us对主要语言使用“无语言标识” -URL()。并将带有翻译后的SEO代码的URL用于子语言:http://www.domain.com/nl/over-onshttp://www.domain.com/de/uber-uns

我希望我的问题能引起您的注意,他们肯定会破解我的!它确实帮助我在这里解决问题。让我可以回顾一下我以前使用的方法以及即将推出的CMS的想法。

我已经非常感谢您抽出宝贵的时间阅读这些文本!

// Edit #1

我忘了提:__()函数是翻译给定字符串的别名。在此方法中,显然应该有某种后备方法,当尚无翻译可用时,将加载默认文本。如果缺少翻译,则应插入翻译或重新生成翻译文件。


Answers:


114

主题前提

多语言站点包含三个不同方面:

  • 界面翻译
  • 内容
  • 网址路由

尽管它们都以不同的方式互连,但从CMS的角度来看,它们是使用不同的UI元素进行管理的,并且存储方式也不同。您似乎对自己的实现和对前两个的理解充满信心。问题是关于后一个方面的问题- “ URL转换?我们应该这样做吗?应该以什么方式?”

该网址可以由什么组成?

一个非常重要的事情是,不要对IDN感兴趣。取而代之的是支持音译(也:转录和罗马化)。乍一看,IDN对于国际URL似乎是可行的选择,但实际上,它不能按广告宣传工作,原因有两个:

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

实际上,几年前,我在一个基于Yii的项目(可怕的框架,恕我直言)中尝试了IDN方法。在抓取该解决方案之前,我遇到了上述两个问题。另外,我怀疑这可能是攻击媒介。

可用选项...如我所见。

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

  • http://site.tld/[:query][:query]决定语言和内容选择的地方

  • http://site.tld/[:language]/[:query][:language]URL的一部分定义语言的选择,[:query]仅用于标识内容

查询为Α和Ω..

假设您选择http://site.tld/[:query]

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

  • $_COOKIE['lang']该特定浏览器的价值
  • HTTP Accept-Language (1)(2)标头中的语言列表

首先,您需要将查询与已定义的路由模式之一进行匹配(如果您选择的是Laravel,请在此处阅读)。成功匹配模式后,您需要查找语言。

您将必须遍历模式的所有部分。找到所有这些片段的潜在翻译,并确定使用哪种语言。当(不是“如果”)出现冲突时,将使用两个附加来源(cookie和标头)来解决路由冲突。

例如:http://site.tld/blog/novinka

这是音译"блог, новинка",在英语中大约是"blog", "latest"

您已经注意到,俄语中的“блог”将译为“博客”。这意味着对于您的第一部分[:query](在最佳情况下),最终会['en', 'ru']列出可能的语言。然后进入下一个片段-“ novinka”。可能的列表中可能只有一种语言:['ru']

当列表中有一项时,您已成功找到该语言。

但是,如果最终得到2种(例如:俄语和乌克兰语)或更多种可能性..或0种可能性(视情况而定)。您将必须使用Cookie和/或标题才能找到正确的选项。

如果所有其他方法均失败,则选择站点的默认语言。

语言作为参数

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

还有另一种语言来源:cookie值。但是,这里没有必要搞乱Accept-Language标头,因为在“冷启动”的情况下(用户首次使用自定义查询打开网站时),您不必处理未知数量的可能的语言。

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

  1. 如果[:language]设置了细分,请使用它
  2. 如果$_COOKIE['lang']设置,请使用
  3. 使用默认语言

使用该语言时,您只需尝试翻译查询,如果翻译失败,则为该特定段使用“默认值”(基于路由结果)。

这不是第三种选择吗?

是的,从技术上讲,您可以结合使用这两种方法,但这会使流程复杂化,并且只适合那些想要手动更改URL http://site.tld/en/newshttp://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'标题是。纯粹用于SEO。

可以在Laravel中完成吗?

Kinda,但默认情况下不是。

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

已路由。现在怎么办?

结果,您最终将获得两条有价值的信息:当前语言和查询的翻译段。然后可以将这些值用于调度到将产生结果的类。

基本上,以下网址:(http://site.tld/ru/blog/novinka或不含的版本'/ru')变成了类似

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

您仅用于调度的对象:

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

..或它的某些变体,具体取决于特定的实现。


1
感谢您提供另外的见解!非常体贴!我当时也在考虑在URL中使用language参数。这似乎是识别特定语言的最佳方法,不仅对于用户,而且对于SEO目的。如果用户将/ en / news更改为/ de / news,例如,我的想法是进行301(永久)重定向到/ de / nachrichten。为了确保每种语言每页只有一个唯一的URL(再次出于SEO目的)
Joshua-Pendo 2013年

选择最佳答案的难度越来越大,目前大约有3/4个答案每个都应至少得到赏金的一部分。结合起来,它们成为我想共同解决的所有问题的可靠答案:)
约书亚-Pendo 2013年

我接受了您的答案,以便为您提供有关URL转换的详细答案的至少最少的代表。高度赞赏!但是,赏金是您下方人员的奖励,因为他以独立于平台的方式回答了我问题的各个方面。
约书亚-Pendo

52

如Thomas Bley所建议,使用预处理器来实现i18n而不会影响性能

在工作中,我们最近在几个属性上实现了i18n的实现,而我们一直在努力解决的事情之一就是处理即时翻译带来的性能损失,然后我发现了Thomas Bley撰写的这篇很棒的博客文章这启发了我们使用i18n处理大流量负载而性能问题降至最低的方式。

与其为每个翻译操作调用函数(这在PHP中是昂贵的),不如使用占位符定义基本文件,然后使用预处理器来缓存这些文件(我们存储文件修改时间以确保我们正在提供服务始终提供最新内容)。

翻译标签

Thomas使用{tr}{/tr}标记来定义翻译的开始和结束位置。由于我们使用的是TWIG,因此我们不想使用{来避免混淆,因此我们使用[%tr%][%/tr%]来代替。基本上,这看起来像这样:

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

请注意,Thomas建议在文件中使用基本英语。我们不这样做,是因为如果我们更改英语值,则不需要修改所有翻译文件。

INI文件

然后,我们为每种语言创建一个INI文件,格式为placeholder = translated

// 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_spliton \n或获取密钥对=,并使CMS能够写入INI文件,这将是微不足道的。

预处理器组件

本质上,Thomas建议使用这样的即时“编译器”(尽管实际上是预处理器)功能来获取翻译文件并在磁盘上创建静态PHP文件。这样,我们实质上缓存了翻译后的文件,而不是为文件中的每个字符串调用翻译函数:

// 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;
}

注意:我没有验证该正则表达式是否有效,也没有从我们公司的服务器中复制它,但是您可以看到该操作的工作原理。

怎么称呼它

同样,此示例来自Thomas Bley,而不是我:

// 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参数结合使用以覆盖语言,但我建议不要使用subdomain-per-language或page-per-language,因为这样会更难查看哪些页面受欢迎,并且会降低入站的价值链接,因为它们几乎不会传播。

为什么要使用这种方法?

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

  1. 通过不为几乎不变的内容调用全部函数而获得了巨大的性能提升(使用此系统,法语的10万访问者仍然只能运行一次翻译替换)。
  2. 它不添加任何负载到我们的数据库,因为它使用简单的平面文件,并且是纯PHP解决方案。
  3. 在我们的翻译中使用PHP表达式的能力。

获取翻译的数据库内容

我们只需要在数据库中为内容添加一列language,然后对LANG前面定义的常量使用访问器方法,因此我们的SQL调用(不幸的是使用ZF1)如下所示:

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

我们的文章具有复合主键idlanguage因此文章54可以以所有语言存在。我们的LANG默认en_US,如果没有指定。

URL Slug转换

我在这里结合了两件事,一是引导程序中的函数,该函数接受$_GET语言参数并覆盖cookie变量,另一是路由,其接受多个子段。然后,您可以在路由中执行以下操作:

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

这些可以存储在平面文件中,可以从管理面板轻松写入。JSON或XML可能会提供支持它们的良好结构。

关于其他几种选择的注意事项

基于PHP的即时翻译

我看不出它们比预处理翻译有什么优势。

基于前端的翻译

我很早就发现了这些有趣的东西,但是有一些警告。例如,您必须向用户提供您打算翻译的网站上的短语的整个列表,如果网站的某些区域要隐藏或不允许他们访问,这可能会出现问题。

您还必须假设所有用户都愿意并且能够在您的网站上使用Javascript,但是根据我的统计,大约有2.5%的用户在没有Java的情况下运行(或者使用Noscript阻止我们的网站使用它) 。

数据库驱动的翻译

PHP的数据库连接速度实在不值得一提,这增加了在要翻译的每个短语上调用函数的本来就很昂贵的开销。这种方法似乎会淹没性能和可伸缩性问题。


我看到我把“前端翻译”弄糊涂了,我的意思是解析屏幕上翻译后的字符串的一种方法。我肯定不是在寻找一种在客户端进行翻译的方法!我的意思是在前端切换语言的最简单方法,但这显然是使用cookie或用户设置的:)
Joshua-Pendo 2013年

哦,通过数据库驱动,我的目标是管理所有翻译的方法,所以我的理想解决方案是将翻译写入数据库的后端,然后是一个生成预处理组件的函数,该组件会生成PHP文件。Why?:简单..我不想被文本的微小变化所困扰,用户应该能够自己使用文本编辑器和/或ftp程序:)
Joshua-Pendo

@PENDO我知道您的意思不是前端翻译,对于那些建议使用JS的前端翻译框架的用户来说,这是薄薄的阴影。;)
Glitch Desire

@PENDO我同意,我会按照您的建议使用后端,但是出于性能方面的考虑,我会使用平面文件代替数据库。当然,这里的核心建议是预渲染后改变模板,这样你可以更换.INI带有一个3列的数据库表文件placeholderreplacementlanguage。在复合键placeholderlanguage。然后使用另一个2列,并带有tempfile(模板的路径)和modified(DATETIME)。
Glitch Desire

1
@PENDO谢谢。我已经备份了250张,并且我打算在网站允许我的情况下,在24小时之内将其奖励给teresko,因为您选择的答案都是正确的,并且我认为拆分最好地体现了您的意图。
Glitch Desire

15

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

使用gettext,您将拥有一个功能强大的工具,其中许多情况已经实现,例如复数形式的数字。用英语只有两种选择:单数和复数。但是以俄语为例,它有3种形式,其不如英语简单。

另外,许多翻译员已经具有使用gettext的经验。

看一下CakePHPDrupal。都启用了多语言。CakePHP是接口本地化的示例,Drupal是内容转换的示例。

对于l10n,根本不是使用数据库。大量查询。标准方法是在早期(或在您更喜欢延迟加载的情况下首次调用i10n函数时)将所有l10n数据存储在内存中。它可以一次从.po文件或DB中读取所有数据。而不是仅仅从数组中读取请求的字符串。

如果您需要实现在线工具来转换界面,则可以将所有这些数据保存在DB中,但是仍然可以将所有数据保存到文件中以供使用。为了减少内存中的数据量,您可以将所有翻译后的消息/字符串分成几组,然后在可能的情况下仅加载所需的组。

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

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

清楚表明法语内容:http : //example.ca/fr/vélo-de-montagne.html

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

退房: http://www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 HTTP:/ /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

翻译内容是比较困难的任务。我认为不同类型的内容(例如文章,菜单项等)会有一些差异。但是在#4中,您的方法正确。看看Drupal有更多想法。它具有足够清晰的数据库架构和足够好的翻译接口。就像您创建文章并为其选择语言一样。而且,以后您可以将其翻译为其他语言。

Drupal翻译界面

我认为URL链接不是问题。您可以为块创建单独的表,这将是正确的决定。同样使用正确的索引,即使有大量数据,查询表也不成问题。而且它不是全文本搜索,而是字符串匹配(如果将使用varchar数据类型作为数据段),并且您也可以在该字段上具有索引。

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


感谢您抽出宝贵时间回答我的问题。您的英语足以让我理解!我已经为您+1了!
约书亚-Pendo

雅罗斯拉夫,再次感谢您的回答。但是,我给出了另外两个答案,其中还有一些更完整的内容,并解释了代码背后使用的方法,而不是指出它已经存在。
约书亚-Pendo

2
没问题。实际上,答案也更完整,更有趣,我也可以阅读。但我希望您也能从我的回答中得到一些有用的信息。
Yaroslav

12

这取决于您的网站有多少内容。最初,我像这里的所有其他人员一样使用数据库,但是编写数据库的所有工作脚本可能很耗时。我并不是说这是一种理想的方法,尤其是当您有大量文本时,但是如果您想不使用数据库而快速进行操作,则此方法可以工作,但是您不能允许用户输入数据它将用作翻译文件。但是,如果您自己添加翻译,它将起作用:

假设您有以下文字:

Welcome!

您可以在带有翻译的数据库中输入此内容,但也可以这样做:

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

现在,如果您的网站使用Cookie,则可以使用以下示例:

$_COOKIE['language'];

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

$language=$_COOKIE['language'];

如果您的Cookie语言是威尔士语,并且您有以下这段代码:

echo $welcome[$language];

其结果将是:

Croeso!

如果您需要为您的网站添加大量翻译内容,并且数据库太消耗资源,则使用数组可能是理想的解决方案。


1
这是我所要求的答案附近的地方。此外,与其在每个页面上提供所有语言,还不如创建lang.en.php包含和使用$lang['welcome']在每个文件中声明的文件之类的文件。
约书亚-Pendo 2014年

7

我建议您不要真正依赖数据库进行翻译,这可能确实是一个麻烦的任务,并且在数据编码的情况下可能是一个极端的问题。

前段时间我遇到了类似的问题,并写了下课来解决我的问题

对象:语言环境\语言环境

<?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;
  }
}

用法

 <?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标头

数据库将以何种方式混乱?因为可能使用不同语言的字符?到目前为止,我主要拥有英语,法语,荷兰语,德语网站,因此目前还没有问题。感谢您的回答,但由于它只是答案的一部分,因此不会赢得赏金。
约书亚-Pendo

好吧,我想您的问题仅对您有所帮助,只会有些人会考虑使用印地语,泰语,中文和阿拉伯语(这些语言将需要更多的1个字节来代表字符)来代替您所需的语言。如果您使用的是db,那么utf8_general_ci排序规则是合适的方法。
Shushant

我同意,我在那里有一些追踪的机会。感谢您指出,多位字符也很重要,足以在此问题中提及:)
约书亚-Pendo 2013年

5

只是一个简单的答案:绝对使用在其前面带有语言标识符的翻译后的url:http : //www.domain.com/nl/over-ons
Hybride解决方案趋于复杂,因此我会坚持使用。为什么?因为网址对于SEO是必不可少的。

关于数据库翻译:语言的数量或多或少是固定的吗?还是不可预测且动态的?如果它是固定的,我将添加新列,否则将添加多个表。

但是通常,为什么不使用Drupal?我知道每个人都希望构建自己的CMS,因为它速度更快,更精简,等等。但这确实是一个坏主意!


1
感谢您的回答。我不想使用Drupal / Joomla的原因很简单:我想确保我了解我的系统的所有来龙去脉,可能出现的漏洞,代码的构建方式(而且很重要:不要由300个程序员共同构建) 。我有足够多的理由不选择开源。除此之外,我希望我的公司成为我的客户的重要因素,让他们去找其他任何开发人员而让我一无所获是一件坏事。
约书亚-Pendo 2013年

7
我认为所有这些原因在大量文章中都有争议。希望您的客户不会完全选择您,因为您拥有其他人无法维护的专有CMS。但是无论如何,那是完全不同的讨论。
雷米2013年

1
我理解您的观点,但我仍然更喜欢一个我了解所有内容的系统,并且在使用插件时,我对依靠其他人的工作没有任何感觉。
约书亚-Pendo

1
此外,由于我是一支“单兵部队”,为我工作的人们应该不难理解系统,因此我倾向于将工作记录得足够好。
约书亚-Pendo

坏主意是选择Drupal,甚至Google都说他们不在乎url是否被翻译。但是,它必须包含一个区域设置标识符。
undefinedman

5

我不会尝试完善已经给出的答案。相反,我将告诉您我自己的OOP PHP框架处理翻译的方式。

在内部,我的框架使用诸如en,fr,es,cn之类的代码。数组包含网站支持的语言:array('en','fr','es','cn')语言代码通过$ _GET(lang = fr)传递,如果未传递或无效,则通过设置为数组中的第一种语言。因此,在程序执行过程中的任何时候以及从一开始就知道当前的语言。

了解典型应用程序中需要翻译的内容类型非常有用:

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

第一种类型很容易理解。基本上,我们在谈论诸如“无法连接到数据库...”之类的消息。仅在发生错误时才需要加载这些消息。我的经理类收到来自其他类的调用,并使用作为参数传递的信息简单地转到相关的类文件夹并检索错误文件。

错误消息的第二种类型更像是表单验证错误时收到的消息。(“您不能留空...”或“请选择一个超过5个字符的密码”)。在类运行之前需要加载字符串。我知道是什么

对于实际的页面内容,我为每种语言使用一个表,每个表均以该语言的代码作为前缀。因此,en_content是具有英语语言内容的表,es_content是西班牙的内容,cn_content是中国的内容,fr_content是法语的内容。

第四种字符串与整个网站都相关。通过使用该语言的代码命名的配置文件(即en_lang.php,es_lang.php等)加载该文件。在全局语言文件中,您需要在英语全局文件中加载翻译后的语言,例如array('English','Chinese','Spanish','French')和array('Anglais','Chinois','法语文件中的Espagnol”,“ Francais”)。因此,当您填充用于语言选择的下拉列表时,它使用的是正确的语言;)

最后,您具有特定于脚本的字符串。因此,如果您编写烹饪应用程序,则可能是“您的烤箱不够热”。

在我的应用程序周期中,首先加载全局语言文件。在这里,您不仅可以找到全局字符串(例如“ Jack's Website”),还可以找到某些类的设置。基本上任何与语言或文化相关的事物。其中的某些字符串包括日期掩码(MMDDYYYY或DDMMYYYY)或ISO语言代码。在主语言文件中,我包括单个类的字符串,因为它们太少了。

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

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

如果出了什么问题,管理人员就会知道从何处获取与语言有关的错误文件。仅在出现错误时加载该文件。

因此结论很明显。在开始开发应用程序或框架之前,请考虑翻译问题。您还需要一个包含翻译的开发工作流程。通过我的框架,我用英语开发了整个网站,然后翻译了所有相关文件。

只是实现翻译字符串的方式的快速总结。我的框架有一个全局变量$ manager,该全局变量运行可用于任何其他服务的服务。因此,例如,表单服务获得了html服务并使用它来编写html。我系统上的服务之一是翻译器服务。$ translator-> set($ service,$ code,$ string)设置当前语言的字符串。语言文件是此类语句的列表。$ translator-> get($ service,$ code)检索翻译字符串。$ code可以是数字,例如1,也可以是字符串,例如'no_connection'。服务之间不会发生冲突,因为每个服务在转换程序的数据区域中都有自己的名称空间。

我将其发布在这里,希望它能像别人几年前一样省去重塑轮子的任务。


4

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

  1. 只需使用具有参数pageId(或#2中描述的objectId,objectTable),目标语言和后备(默认)语言的可选参数的函数__()。可以在某些全局配置中设置默认语言,以便日后更轻松地进行更改。

  2. 为了将内容存储在数据库中,我使用了以下结构:(pageId,语言,内容,变量)。

    • pageId将是您要翻译的页面的FK。如果您还有其他对象,例如新闻,画廊或其他任何对象,只需将其分为2个字段objectId,objectTable。

    • 语言-很明显它将存储ISO语言字符串EN_en,LT_lt,EN_us等。

    • 内容-您要翻译的文本以及用于替换变量的通配符。示例“您好,%% name %%。您的帐户余额为%% balance %%”。

    • variables-json编码的变量。PHP提供了快速解析这些函数的功能。示例“名称:Laurynas,余额:15.23”。

    • 您提到的也是子弹场。您可以将其自由添加到此表中,只是为了快速搜索它。

  3. 通过缓存翻译,必须将数据库调用减少到最少。它必须存储在PHP数组中,因为它是PHP语言中最快的结构。如何进行此缓存取决于您。根据我的经验,应该为每种受支持的语言提供一个文件夹,为每种pageId提供一个数组。更新翻译后,应重新构建缓存。仅应重新生成更改后的数组。

  4. 我想我在#2中回答了

  5. 您的想法完全合乎逻辑。这很简单,我认为不会给您带来任何问题。

URL应该使用转换表中存储的段进行转换。

最后的话

研究最佳做法始终是件好事,但不要重蹈覆辙。只需采用并使用知名框架中的组件并使用它们即可。

看一下Symfony翻译组件。对于您来说,这可能是一个很好的代码库。


感谢您的评论,请为您的时间加+1。如果我没有记错的话,Laravel(在我的情况下)正在使用一些Symfony零件,因此您绝对不应该重新发明轮子。我开始提出这个问题(并赏金)以了解其他人的翻译方式,我开始相信那里有很多最佳实践:-)
约书亚-Pendo 2013年

1

我已经一遍又一遍地问自己有关的问题,然后迷上了正式语言……但只是为了帮助您一点,我想分享一些发现:

我建议看一下高级CMS

Typo3对于PHP (我知道有很多东西,但是那是我认为最成熟的东西)

PlonePython

如果您发现2013年的网络应该有所不同,那么请从头开始。这意味着将组建一支由高技能/经验丰富的人员组成的团队来构建新的CMS。也许您想为此目的看一下聚合物。

如果涉及编码和多语言网站/本地语言支持,我认为每个程序员都应该对unicode有一些了解。如果您不知道unicode,则肯定会弄乱您的数据。不要使用成千上万的ISO代码。他们只会为您节省一些内存。但是您几乎可以使用UTF-8进行任何操作,甚至可以存储中文字符。但是为此,您需要存储2或4个字节的字符,这使得它基本上是utf-16或utf-32。

如果是关于URL编码的,那么您也不应该再混合使用编码,并且要知道,至少对于域名,存在由不同的大厅定义的规则,这些规则提供了类似浏览器的应用程序。例如,一个域可能非常相似,例如:

ьankofamerica.com或bankofamerica.com samesamebutdifferent;)

当然,您需要文件系统才能使用所有编码。使用utf-8文件系统的unicode的另一个优点。

如果涉及翻译,请考虑文档的结构。例如一本书或一篇文章。您具有docbook了解这些结构的规范。但是在HTML中,它仅与内容块有关。因此,您希望在该级别(在网页级别或域级别)进行翻译。因此,如果不存在某个块,则该块不存在,如果一个网页不存在,您将被重定向到较高的导航级别。如果一个域的导航结构应该完全不同,则..它是一个完全不同的结构来管理。这已经可以用Typo3完成。

如果它是关于框架的,那是我所知道的最成熟的框架,它可以做MVC之类的通用内容(流行语,我真的很讨厌它!就像“性能”一样。地狱)是Zend。将标准引入php混沌编码器已被证明是一件好事。但是,Typo3除了CMS之外还具有一个Framework。最近,它已经重新开发,现在称为flow3。当然,这些框架涵盖了数据库抽象,模板和缓存概念,但各有千秋。

如果它关于缓存...那可能是非常复杂/多层的。在PHP中,您将考虑加速器,操作码,以及html,httpd,mysql,xml,css,js ...任何类型的缓存。当然,某些部分应该被缓存,动态部分(例如博客答案)不应该被缓存。应该通过AJAX请求一些生成的URL。JSON,hashbangs

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

你也想做出统计,或许已经分发系统/ Facebook的等任何软件的Facebook上建造你的洁癖CMS之上......所以你需要不同类型的数据库inmemory,bigdata,XML,任何。

好吧,我认为目前为止就足够了。如果您还没有听说过typo3 / plone或提到的框架,那么您有足够的知识来学习。在那条路上,您会发现许多尚未解决的问题的解决方案。

如果您当时认为,让我们创建一个新的CMS,因为它的2013和php即将死亡,那么欢迎您加入任何其他开发人员小组,希望他们不会迷路。

祝好运!

顺便说一句。人们将来将不会再有任何网站呢?我们都将在google +上吗?我希望开发人员更具创造力,并做一些有用的事情(不要被笨蛋所吸收)

////编辑///对于现有应用程序,请稍加思考:

如果您有php mysql CMS,并且想嵌入多语言支持。您可以将表与带有任何语言的附加列一起使用,也可以在同一个表中插入带有对象ID和语言ID的译文,或者为任何语言创建相同的表并在其中插入对象,然后根据需要进行选择并集将它们全部显示出来。对于数据库,请使用utf8常规ci,当然在前端/后端也请使用utf8文本/编码。我已经按照您已经说明的方式使用了网址路径段作为网址

domain.org/en/about可以将lang ID映射到您的内容表。无论如何,您都需要为您的url建立一个参数映射,以便您想定义一个要从URL中的pathegment映射的参数,例如

domain.org/en/about/employees/IT/administrators/

查找配置

pageid | 网址

1 | /about/employees/../ ..

1 | /../about/employees../../

将参数映射到网址路径“”

$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;
}

可以说,多数民众赞成已经在高层职位上得到了报道。

而且不要忘记,您需要将URL“重写”到生成的php文件中,该文件在大多数情况下将是index.php。


感谢您的评论,我当然应该考虑一些事情。我已经使用utf8编码已有两年了,曾经曾经与字符打交道;-)另一方面,CMS / Framework的类型对您的回答没有任何影响,因为我正在寻找一个平台无关的方法,就好像我们是从头开始编写代码一样。
约书亚-Pendo

如果您真的想从头开始编写代码,建议您看一下Dartlang和Polymer。由于dartlang在浏览器中运行,并且具有32位和64位支持,并且可以在服务器端用于大多数目的,并且具有dart2js编译器,这确实值得研究。如果人们谈论平台独立性,那么他们就会想到Java……我们知道这意味着什么。构建过程...我想我将使用JSON进行交换。生成具有hashbangs和serverside的网站客户端。做任何您想确保协作的事情。
Dama博士

数据库布局和生成逻辑是主要任务。没有人会在这里为您做这件事……但是这个想法本身才是最重要的。由于我不在乎大厅,而是想把事情做好,所以我希望您可以创建模型并共享一些东西。我现在正在从事类似的任务。但我仍在计划中。我正在考虑将Typo3作为后端并创建一个新的客户端结构。多语言模式在后端得到解决,并将以专用于搜索引擎/网络服务的方式共享信息。无论如何,这都是与上下文相关的,并且是持续的构建任务
Dama博士,

-1

数据库工作:

创建语言表“语言”:

领域:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

在数据库“内容”中创建一个表:

领域:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

前端工作:

当用户从下拉菜单或任何区域中选择任何一种语言,然后在会话中保存所选的语言ID,例如,

$_SESSION['language']=1;

现在,根据会话中存储的语言ID从数据库表“内容”中获取数据。

可以在这里找到详细信息 http://skillrow.com/multilingual-website-in-php-2/


1
这是一种随后需要进行简单语言集成的方法,您甚至尝试阅读完整的文章并给出答案吗?
约书亚-Pendo

-2

作为一个住在魁北克的人,几乎所有站点都是法语和英语...我尝试了很多(如果不是大多数)用于WP的多语言插件... mQtranslate是在我所有站点上都能正常工作的唯一有用的解决方案...我生活并死了!

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


1
是的,WP不是问题的任何因素。这本来可以是评论性的评论
约书亚

-3

什么WordPress的 + MULTI-LANGUAGE SITE BASIS(插件)?该站点将具有以下结构:

  • example.com/ eng / category1 / ....
  • example.com/ eng / my-page ....
  • example.com/ rus / category1 / ....
  • example.com/ rus / my-page ....

该插件通过简单的逻辑为所有短语提供翻译接口:

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

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

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


3
并且不是西班牙语中的“ Holla userio”是“ Hola Usuario”
bheatcoker

1
哈哈Holla userio,真有趣!
spekdrum

由于我不知道西班牙语(仅用于示例)的原因,快点赶快投票!!:)
T.Todua

-5

www.multilingualizer.com是一个非常简单的选项,可以在可以上传Javascript的任何网站上使用,

它使您可以将所有语言的所有文本放在一页上,然后隐藏用户不需要查看的语言。效果很好。


当心,SEO会非常糟糕!另外,您只需要一部分内容就可以加载所有内容,这确实是不好的做法。
Hafenkranich

这个网站只有英文的怪异东西...为什么他们不使用自己的解决方案?
eduardo.lopes 18-3-9的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.