196

几个月来我一直在为这个问题苦苦挣扎,但我之前没有遇到过需要探索所有可能选择的情况。现在,我觉得是时候了解这些可能性并创建我自己的个人偏好,以便在我即将进行的项目中使用。

让我先勾勒一下我正在寻找的情况

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

应该翻译什么

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

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

我自己想出了什么

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

  1. PHP Parsed Templates : 模板系统应该被 PHP 解析。这样我就可以将翻译后的参数插入到 HTML 中,而无需打开模板并修改它们。除此之外,PHP 解析的模板使我能够为完整的网站拥有 1 个模板,而不是为每种语言都拥有一个子文件夹(我以前有过)。达到这个目标的方法可以是 Smarty、TemplatePower、Laravel's Blade 或任何其他模板解析器。正如我所说,这应该独立于书面解决方案。
  2. 数据库驱动:也许我不需要再提这个了。但解决方案应该是数据库驱动的。CMS 的目标是面向对象和 MVC,所以我需要考虑字符串的逻辑数据结构。因为我的模板是结构化的:templates/Controller/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; . 我想这是最好的方法,因为渲染的每个视图都可以包含它自己的语言文件(如果存在)。然后应该将语言参数加载到特定视图而不是全局范围内,以防止参数相互覆盖。
  4. 数据库表翻译:这其实是我最担心的事情。我正在寻找一种方法来创建新闻/页面/等的翻译。尽快。每个模块有两个表(例如NewsNews_translations)是一种选择,但感觉要获得一个好的系统需要做很多工作。我想出的一件事是基于data versioning我写的一个系统:有一个数据库表名Translations,这个表有一个唯一的组合languagetablenameprimarykey. 例如:en_En / News / 1(指的是英文版的 ID=1 的 News 条目)。但是这种方法有两个巨大的缺点:首先,由于数据库中有大量数据,该表往往会变得很长,其次,使用这种设置来搜索表将是一项艰巨的工作。例如,搜索该项目的 SEO slug 将是一个全文搜索,这非常愚蠢。但另一方面:这是在每个表格中非常快速地创建可翻译内容的快速方法,但我不相信这个专业人士超过了骗局。
  5. 前端工作:前端也需要一些思考。当然,我们会将可用的语言存储在数据库中,并(停用)我们需要的语言。这样,脚本可以生成一个下拉菜单来选择一种语言,并且后端可以自动决定使用 CMS 可以进行哪些翻译。然后,在获取语言文件以供查看或获取网站上内容项的正确翻译时,将使用所选语言(例如 en_EN)。

所以,他们在那里。到目前为止我的想法。它们甚至不包括日期等的本地化选项,但由于我的服务器支持 PHP5.3.2+,最好的选择是使用 intl 扩展,如下所述:http: //devzone.zend.com/1500/internationalization-in -php-53/ - 但这将在以后的任何开发体育场中使用。目前的主要问题是如何对网站内容进行最佳翻译。

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

网址翻译?我们应该这样做还是不这样做?以什么方式?

所以.. 如果我有这个 url:http://www.domain.com/about-us并且英语是我的默认语言。http://www.domain.com/over-ons当我选择荷兰语作为我的语言时,是否应该将这个 URL 翻译成?或者我们应该走简单的路,简单地更改在 处可见的页面内容/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对主要语言使用“language-identification-less”-URL ( )。并使用带有翻译的 SEO slug 的 URL 用于子语言:http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

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

我要感谢你花时间阅读这堆文字!

// Edit #1

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

4

14 回答 14

125

题目的前提

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

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

虽然它们都以不同的方式相互连接,但从 CMS 的角度来看,它们使用不同的 UI 元素进行管理并以不同的方式存储。您似乎对前两个的实施和理解充满信心。问题是关于后者—— “URL 翻译?我们应该这样做还是不这样做?以什么方式?”

URL 可以由什么组成?

一件非常重要的事情是,不要对IDN有好感。而是倾向于音译(也:转录和罗马化)。虽然乍一看 IDN 似乎是国际 URL 的可行选择,但它实际上并不像宣传的那样工作,原因有两个:

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

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

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

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

  • http://site.tld/[:query]: where[: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. 使用默认语言

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

这不是第三种选择吗?

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

但即使是这种情况,也可以使用 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 中完成吗?

有点,但不是默认的。

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

是路由的。现在怎么办?

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

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

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

您仅用于调度:

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

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

于 2013-10-17T15:31:09.390 回答
54

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

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

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

翻译标签

Thomas 使用{tr}{/tr}标记来定义翻译的开始和结束位置。由于我们使用的是 TWIG,我们不想使用{以避免混淆,所以我们使用[%tr%]and[%/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参数结合使用以覆盖语言,但我不建议按语言使用子域或按语言页面,因为这样会更难查看哪些页面很受欢迎,并且会降低入站的价值链接,因为你会让它们几乎没有传播。

为什么使用这种方法?

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

  1. 不为很少更改的内容调用一大堆函数带来的巨大性能提升(使用这个系统,100k 法语访问者最终仍只会运行一次翻译替换)。
  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 蛞蝓翻译

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

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

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

关于其他一些选项的说明

基于 PHP 的即时翻译

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

基于前端的翻译

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

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

数据库驱动的翻译

PHP 的数据库连接速度没什么好写的,这增加了在每个短语上调用函数以进行翻译的本已很高的开销。这种方法似乎无法解决性能和可扩展性问题。

于 2013-10-17T11:29:06.880 回答
16

我建议你不要发明轮子,使用 gettext 和 ISO 语言缩写列表。你见过 i18n/l10n 如何在流行的 CMS 或框架中实现吗?

使用 gettext 您将拥有一个强大的工具,其中许多案例已经像复数形式的数字一样实现。在英语中,您只有 2 个选项:单数和复数。但在俄语中,例如有 3 种形式,它不像英语那样简单。

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

看看CakePHPDrupal。都启用了多语言。CakePHP 作为界面本地化的例子,Drupal 作为内容翻译的例子。

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

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

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

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

清楚地表明法语内容: 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 slug 没有问题。您可以为 slug 创建单独的表,这将是正确的决定。同样使用正确的索引即使有大量数据也可以查询表。它不是全文搜索,而是字符串匹配,如果将使用 varchar 数据类型作为 slug 并且您也可以在该字段上建立索引。

PS 对不起,我的英语远非完美。

于 2013-10-11T03:05:14.157 回答
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!

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

于 2014-07-08T17:31:49.713 回答
8

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

我之前遇到过类似的问题并写了以下课程来解决我的问题

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

<?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标题
于 2013-10-16T07:15:42.900 回答
5

只是一个子答案:绝对使用带有语言标识符的翻译网址:http: //www.domain.com/nl/over-ons
Hybride 解决方案往往会变得复杂,所以我会坚持下去。为什么?因为 url 对于 SEO 至关重要。

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

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

于 2013-10-09T07:08:56.753 回答
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 的网站”),还可以找到某些类的设置。基本上任何与语言或文化相关的东西。其中的一些字符串包括日期掩码(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”这样的字符串。服务之间不会发生冲突,因为每个服务在翻译器的数据区域中都有自己的命名空间。

我把这个贴在这里,希望它能像我几年前所做的那样,让人们免于重新发明轮子的任务。

于 2015-03-16T05:03:18.550 回答
4

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

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

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

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

    • 语言 - 显然它会存储 ISO 语言字符串 EN_en、LT_lt、EN_us 等。

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

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

    • 你还提到了蛞蝓领域。您可以自由地将其添加到此表中,以便快速搜索它。

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

  4. 我想我在#2中回答了这个问题

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

URL 应该使用翻译表中存储的 slug 进行翻译。

最后的话

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

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

于 2013-10-12T10:54:46.030 回答
1

我一遍又一遍地问自己相关的问题,然后迷失在正式语言中......但只是为了帮助你一点,我想分享一些发现:

我建议看一下高级 CMS

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

PlonePython

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

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

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

ьankofamerica.com 或 bankofamerica.com 相同但不同 ;)

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

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

如果它是关于框架,我所知道的最成熟的框架,来做像 MVC 这样的通用东西(我真的讨厌它!像“性能”)如果你想卖东西,使用性能和功能丰富这个词然后你卖......见鬼)是Zend。事实证明,将标准引入 php 混沌编码器是一件好事。但是,typo3 除了 CMS 之外还有一个框架。最近它已经重新开发,现在称为flow3。当然,这些框架涵盖了数据库抽象、模板和缓存概念,但也有各自的优势。

如果它关于缓存......那可能非常复杂/多层。在 PHP 中,您会想到加速器、操作码,还有 html、httpd、mysql、xml、css、js ......任何类型的缓存。当然,某些部分应该被缓存,而动态部分(如博客答案)则不应该被缓存。应该通过 AJAX 使用生成的 url 请求一些。JSON、hashbangs等。

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

此外,您还想进行统计,可能有分布式系统/facebook 等。任何要构建在您的顶级 cms 之上的软件......所以您需要不同类型的数据库inmemory、bigdata、xml等等.

好吧,我认为现在就足够了。如果您还没有听说过typo3 / plone 或提到的框架,那么您已经足够学习了。在这条道路上,您会找到很多您尚未提出的问题的解决方案。

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

祝你好运!

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

//// 编辑 /// 对您现有的应用程序稍加思考:

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

domain.org/en/about 您可以将 lang ID 映射到您的内容表。无论如何,您需要为您的网址提供一个参数映射,因此您想定义一个要从您的网址中的路径段映射的参数,例如

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

查找配置

页号| 网址

1 | /关于/员工/../..

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

将参数映射到 url 路径段“”

$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

于 2013-10-18T02:00:47.323 回答
0

制作多语言网站的真正挑战是内容。你将如何存储同一篇文章的不同版本?您使用的是关系数据库还是非关系数据库?

使用 MySQL 等关系数据库,您可以利用JSON数据类型来完全存储同一字段的所有不同版本。

使用非关系数据库时,您可以简单地将不同版本存储在可通过其键识别的同一对象中。

如果您使用 Laravel,您可能会发现Laravel Translatable包在处理传统关系数据库时非常有用。

于 2021-08-20T07:40:22.937 回答
-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/

于 2014-04-04T05:52:50.747 回答
-3

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

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

于 2014-11-11T17:12:40.677 回答
-5

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 但是,请检查插件是否仍然处于活动状态。

于 2015-07-02T14:52:59.313 回答
-6

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

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

于 2016-06-08T16:40:24.880 回答