首页 文章

最佳实践多语言网站

提问于
浏览
160

我已经在这个问题上苦苦挣扎了好几个月,但我还没有遇到过需要探索所有可能的选择的情况 . 现在,我觉得是时候了解可能性并创建我自己的个人偏好,以便在我即将开展的项目中使用 .

Let me first sketch the situation I'm looking for

我现在已经使用了很长一段时间了 . 但是,我将在即将开展的项目中使用Laraval4 . Laravel似乎是更简洁的PHP编码方式的最佳选择 . Sidenote: Laraval4 should be no factor in your answer . 我正在寻找独立于平台/框架的一般翻译方式 .

What should be translated

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

此外,我正在寻找最简单的方法来翻译多个数据库表,而不需要创建额外的表 .

What did I come up with myself

正如我一直在寻找,阅读和尝试自己 . 我有几个选择 . 但我仍然觉得我没有达到我真正寻求的最佳实践方法 . 现在,这是我想出来的,但这种方法也有它的副作用 .

  • PHP Parsed Templates :模板系统应该由PHP解析 . 这种方式我以前有过 . )达到此目标的方法可以是Smarty,TemplatePower,Laravel的Blade或任何其他模板解析器 . 正如我所说,这应该独立于书面解决方案 .

  • Database Driven :也许我不需要再提这个了 . 但解决方案应该是数据库驱动的 . CMS旨在面向对象和MVC,因此我需要考虑字符串的逻辑数据结构 . 由于我的模板将被构造:templates / Controller / View.php也许这个结构最有意义: Controller.View.parameter . 数据库表将使用 value 字段将这些字段设置为long . 在模板内部,我们可以使用一些排序方法,如 echo __('Controller.View.welcome', array('name', 'Joshua')) ,参数包含 Welcome, :name . 因此结果是 Welcome, Joshua . 这似乎是一个很好的方法,因为参数如:name很容易被编辑器理解 .

  • Low Database Load :当然,如果在运行中加载这些字符串,上述系统将导致数据库负载加载 . 因此,我需要一个缓存系统,一旦在管理环境中编辑/保存语言文件,就会重新呈现语言文件 . 由于生成了文件,因此还需要良好的文件系统布局 . 我想我们可以选择 languages/en_EN/Controller/View.php 或.ini,最适合你的 . 也许.ini甚至可以在最后解析得更快 . 这个fould应该包含 format parameter=value; 中的数据 . 我想这是执行此操作的最佳方式,因为呈现的每个View都可以包含它自己的语言文件(如果存在) . 然后,语言参数应加载到特定视图而不是全局范围,以防止参数相互覆盖 .

  • Database Table translation :这实际上就是我在寻找创建新闻/页面等翻译的方法 . 尽快 . 每个模块都有两个表(例如 NewsNews_translations )是一个选项,但是为了获得一个好的系统感觉很有用 . 我想出的一件事是基于我写的 data versioning 系统:有一个数据库表名 Translations ,这个表有 languagetablenameprimarykey 的唯一组合 . 例如:en_En / News / 1(参考ID = 1的新闻项目的英文版本) . 但是这个方法存在两个巨大的缺点:首先,这个表在数据库中有大量数据需要很长时间,其次使用这个设置搜索表是一件很麻烦的工作 . 例如 . 搜索该项目的SEO slug将是一个全文搜索,这是非常愚蠢的 . 但另一方面:它认为这位专业人士过分夸大了骗局 .

  • Front-end Work :前端也需要一些思考 . 当然,我们会将可用语言存储在数据库中,并(de)激活我们需要的语言 . 通过这种方式,脚本可以生成下拉列表以选择语言,后端可以自动决定使用CMS可以进行哪些翻译 . 然后,在获取视图的语言文件或获取内容项的正确翻译时,将使用所选语言(例如en_EN)在网站上 .

所以,他们就是 . 我的想法到目前为止 . 它们甚至还没有包含日期等的本地化选项,但是因为我的服务器支持PHP5.3.2,所以最好的选择是使用intl扩展,如下所述:http://devzone.zend.com/1500/internationalization-in-php-53/ - 但这将在任何后续的开发体育场中使用 . 目前,主要问题是如何在网站上获得最佳的内容翻译实践 .

除了我在这里解释的一切,我还有另一件我尚未决定的事情,它看起来像一个简单的问题,但事实上它让我头痛:

URL Translation? Should we do this or not? and in what way?

所以..如果我有这个网址: http://www.domain.com/about-us ,英语是我的默认语言 . 当我选择荷兰语作为我的语言时,是否应将此URL翻译为 http://www.domain.com/over-ons ?或者我们应该走简单的道路,只需更改 /about 处可见的页面内容 . 最后一件事似乎不是一个有效的选项,因为这会产生同一个URL的多个版本,这个索引内容将以正确的方式失败 .

另一种选择是使用 http://www.domain.com/nl/about-us . 这会为每个内容生成至少一个唯一的URL . 此外,更容易使用其他语言,例如 http://www.domain.com/en/about-us ,并且Google和Google访问者都可以更轻松地了解所提供的网址 . 使用此选项,我们如何处理默认语言?默认语言是否应删除默认选择的语言?因此,将 http://www.domain.com/en/about-us 重定向到 http://www.domain.com/about-us ...在我看来,这是最好的解决方案,因为当CMS只设置一种语言时,不需要在URL中使用此语言标识 .

第三个选项是两个选项的组合:使用"language-identification-less" -URL( http://www.domain.com/about-us )作为主要语言 . 并使用带有翻译的SEO slug的URL用于子语言: http://www.domain.com/nl/over-onshttp://www.domain.com/de/uber-uns

我希望我的问题让你的头开裂,他们肯定会破解我的!它确实帮助我在这里解决问题 . 让我有可能回顾一下我之前使用的方法,以及我对即将推出的CMS的想法 .

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

// Edit #1

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

13 回答

  • 4

    主题的前提

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

    • 界面翻译

    • 内容

    • url路由

    虽然它们都以不同的方式互连,但从CMS的角度来看,它们使用不同的UI元素进行管理并以不同方式存储 . 您似乎对实施和理解前两个有信心 . 问题是关于后一方面 - "URL Translation? Should we do this or not? and in what way?"

    可以制作什么URL?

    一个非常重要的事情是,不要喜欢IDN . 而是赞成transliteration(也是:转录和罗马化) . 虽然乍一看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] segment的内容;还有两个来源:

    该特定浏览器的

    • $_COOKIE['lang']

    • HTTP Accept-Language(1),(2) Headers 中的语言列表

    首先,您需要将查询与定义的路由模式之一匹配(如果您的选择是Laravel,那么read here) . 在成功匹配模式后,您需要找到该语言 .

    你必须经历所有细分的模式 . 查找所有这些细分的潜在翻译并确定使用的语言 . 当它们(而不是“if”)出现时,将使用另外两个源(cookie和标头)来解决路由冲突 .

    举个例子: http://site.tld/blog/novinka .

    这是 "блог, новинка" 的音译,用英语表示约为 "blog", "latest" .

    正如您已经注意到的那样,俄语"блог"将被音译为"blog" . 这意味着,对于 [:query] 的第一部分,您(在最佳情况下)最终会得到 ['en', 'ru'] 可能语言的列表 . 然后你采取下一部分 - "novinka" . 在可能性列表中可能只有一种语言: ['ru'] .

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

    但是,如果你最终得到2(例如:俄罗斯和乌克兰)或更多的可能性..或0种可能性,视情况而定 . 您将不得不使用cookie和/或标头来查找正确的选项 .

    如果一切都失败了,你选择网站的默认语言 .

    语言作为参数

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

    还有一个第二语言来源:cookie值 . 但是这里没有必要弄乱Accept-Language Headers ,因为在“冷启动”的情况下(当用户第一次使用自定义查询打开网站时),您没有处理未知数量的可能语言 .

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

    • 如果设置了 [:language] 段,请使用它

    • 如果设置了 $_COOKIE['lang'] ,请使用它

    • 使用默认语言

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

    这不是第三种选择吗?

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

    但即使是这种情况也可能使用cookie值(其中包含有关以前语言选择的信息)来减轻,以较少的魔力和希望实现 .

    使用哪种方法?

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

    同样在实际情况下,您将在URL中拥有第三个主要部分:“title” . 如在网上商店的产品名称或新闻网站的文章 Headers .

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

    在这种情况下, '/news/article/121415' 将是查询, 'EU-as-global-reserve-currency' 是 Headers . 纯粹用于SEO目的 .

    可以在Laravel完成吗?

    有点,但不是默认 .

    我不太熟悉它,但从我所看到的,Laravel使用简单的基于模式的路由机制 . 要实现多语言URL,您可能需要extend core class(es),因为多语言路由需要访问不同形式的存储(数据库,缓存和/或配置文件) .

    它被发送了 . 现在怎么办?

    因此,您最终会得到两条有 Value 的信息:当前语言和已翻译的查询片段 . 然后可以使用这些值分派到将产生结果的类 .

    基本上,以下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

    在没有性能命中的情况下实现i18n使用Thomas Bley建议的预处理器

    在工作中,我们最近在我们的几个属性上实现了i18n,我们一直在努力解决的问题之一就是处理即时翻译的性能问题,然后我发现this great blog post by Thomas Bley激发了我们的方式'重新使用i18n处理大流量负载,同时将性能问题降至最低 .

    我们知道在PHP中使用占位符来定义我们的基本文件,然后使用预处理器来缓存这些文件(我们存储文件修改时间以确保我们正在服务),而不是为每个翻译操作调用函数最新的内容在任何时候) .

    翻译标签

    Thomas使用 {tr}{/tr} 标签来定义翻译的开始和结束位置 . 由于我们想要使用 { 来避免混淆所以我们改用 [%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内部修改这些内容是非常简单的,只需通过 \n= 上的 preg_split 获取密钥对,并使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 参数结合起来覆盖语言,但是我不会更难看到哪些页面很受欢迎,并且会降低入站链接的 Value ,因为它们会让它们更加难以传播 .

    为什么要使用这种方法?

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

    • 由于没有为很少改变的内容调用一大堆功能而获得巨大的性能提升(使用这个系统,法语中的100k访问者仍然只能运行翻译替换一次) .

    • 它不会向我们的数据库添加任何负载,因为它使用简单的平面文件并且是纯PHP解决方案 .

    • 在我们的翻译中使用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 Translation

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

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

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

    关于其他几种选择的说明

    PHP-based On-The-Fly Translation

    我看不出它们提供了超过预处理翻译的任何优势 .

    Front-end Based Translations

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

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

    Database-Driven Translations

    PHP的数据库连接速度无需写回家,这增加了在每个要翻译的短语上调用函数的高额开销 . 这种方法的性能和可扩展性问题似乎势不可挡 .

  • 7

    我建议你不要发明一个轮子并使用gettext和ISO语言的缩写列表 . 你有没有看到i18n / l10n如何在流行的CMS或框架中实现?

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

    许多翻译人员也有使用gettext的经验 .

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

    对于l10n使用数据库事实并非如此 . 对于查询,它将是吨 . 标准方法是在早期阶段(或者如果您更喜欢延迟加载,在第一次调用i10n函数期间)将所有l10n数据存入内存 . 它可以一次从.po文件或DB中读取所有数据 . 而不仅仅是从数组中读取请求的字符串 .

    如果您需要实现在线工具来翻译界面,您可以将所有数据保存在数据库中,但仍然可以将所有数据保存到文件中以使用它 . 要减少内存中的数据量,您可以将所有已翻译的消息/字符串拆分为组,而不是仅加载所需的组(如果可能的话) .

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

    关于网址 . Google indirectly suggest使用翻译:

    清楚地表明法语内容: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有更多的想法 . 它具有足够清晰的DB模式和足够好的翻译界面 . 就像你创建文章并为它选择语言一样 . 而且您可以在以后将其翻译成其他语言 .

    Drupal translation interface

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

    PS抱歉,我的英语远非完美 .

  • 13

    这取决于您的网站有多少内容 . 起初,我在这里使用了像所有其他人一样的数据库,但编写数据库的所有工作脚本可能非常耗时 . 我不是说这是一种理想的方法,特别是如果你有很多文本,但如果你想在不使用数据库的情况下快速完成,这种方法可行,但是,你不能允许用户输入数据它将用作翻译文件 . 但如果您自己添加翻译,它将起作用:

    假设你有这样的文字:

    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!
    

    如果您需要为您的网站添加大量翻译并且数据库过于消耗,则使用阵列可能是理想的解决方案 .

  • 49

    我建议你不要真正依赖数据库进行翻译它可能真的是一个混乱的任务,在数据编码的情况下可能是一个极端的问题 .

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

    对象:Locale \ Locale

    <?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 Headers
  • -3

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

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

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

  • -1

    在开始使用Symfony框架之前,我之前有过相同的探测器 .

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

    • 用于在数据库中存储内容我使用以下结构:( pageId,语言,内容,变量) .

    • pageId将是您要翻译的页面的FK . 如果你有其他对象,比如新闻,画廊或其他什么,只需将它分成2个字段objectId,objectTable .

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

    • content - 要与变量替换的通配符一起翻译的文本 . 示例“Hello mr . %% name %% . 您的帐户余额为%% balance %% . ”

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

    • 你提到了slug字段 . 您可以自由地将它添加到此表中,只是为了快速搜索它 .

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

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

    • 你的想法完全符合逻辑 . 这个很简单,我认为不会让你有任何问题 .

    应使用转换表中存储的slugs翻译URL .

    Final words

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

    看看Symfony translation component . 它可能是一个很好的代码库 .

  • 4

    我不会试图改进已经给出的答案 . 相反,我会告诉你我自己的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等 . 在全局语言文件中,您需要在英语全局文件和数组中加载翻译的语言,如数组('英语','中文','西班牙语','法语')('Anglais','Chinois',' Espagnol','Francais')在法国文件中 . 因此,当您填充语言选择的下拉列表时,它使用正确的语言;)

    最后,您将拥有特定于脚本的字符串 . 因此,如果你写一个烹饪应用程序,它可能是“你的烤箱不够热” .

    在我的应用程序周期中,首先加载全局语言文件 . 在那里,您不仅可以找到全局字符串(如“Jack的网站”),还可以找到某些类的设置 . 基本上任何语言或文化依赖 . 其中的一些字符串包括日期掩码(MMDDYYYY或DDMMYYYY)或ISO语言代码 . 在主语言文件中,我包含了各个类的字符串,因为它们很少 .

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

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

    如果出现问题,管理员知道从何处获取与语言相关的错误文件 . 该文件仅在出错时加载 .

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

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

    我在这里发布这篇文章是希望它可以为某些人重新设计重新发明轮子的任务,就像我不得不在几年前做的那样 .

  • -4

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

    我建议看一下高级CMS

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

    Plone in Python

    如果您发现2013年的网络应该有所不同,那么从头开始 . 这意味着要组建一支技术精湛/经验丰富的团队来 Build 一个新的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(流行语我真的很讨厌它!喜欢"performance"如果你想卖东西,请使用性能和featurerich这个词然后卖掉......到底是怎么回事)是 Zend . 事实证明,为php混沌编码器带来标准是一件好事 . 但是,除了CMS之外,typo3还有一个框架 . 最近它已被重新开发,现在称为flow3 . 框架当然包括数据库抽象,模板和缓存概念,但具有个人优势 .

    如果它关于缓存......那可能是一个非常复杂/多层次的 . 在PHP中你'll think about accellerator, opcode, but also html, httpd, mysql, xml, css, js ... any kinds of caches. Of course some parts should be cached and dynamic parts like blog answers shouldn'吨 . 有些应该通过生成的URL请求AJAX . JSON,hashbangs等

    然后,您希望您的网站上的任何小组件仅由特定用户访问或管理,因此从概念上讲,它起着重要作用 .

    你也想制作统计数据,也许有分布式系统/脸书facebook等等任何软件都要 Build 在你的顶级cms之上...所以你需要不同类型的数据库inmemory,bigdata,xml,无论如何 .

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

    如果那时你想,让我们制作一个新的CMS,因为它的2013和php无论如何都要死了,那么你欢迎加入任何其他开发人员团队,希望不会迷路 .

    祝好运!

    顺便说一下 . 人们将来不再拥有任何网站怎么样?我们都会在谷歌上吗?我希望开发人员变得更有创意并做一些有用的事情(不要被borgle同化)

    ////编辑///对现有应用程序稍作思考:

    如果你有一个php mysql CMS,你想嵌入multilang支持 . 你可以将你的表用于任何语言的aditional列,或者在同一个表中插入带有对象id和语言id的翻译,或者为任何语言创建一个相同的表并在那里插入对象,然后根据需要创建一个select union将它们全部显示出来 . 对于数据库,使用utf8 general ci,当然在前端/后端使用utf8 text / encoding . 我已经像你已经解释过的那样使用url路径段作为url

    domain.org/en/about您可以将lang ID映射到您的内容表 . 无论如何,你需要为你的网址设置一个参数 Map ,这样你就可以定义一个参数来映射你网址中的路径段,例如

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

    查找配置

    的pageid |网址

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

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

    将参数映射到url pathsegment“”

    $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

  • 10

    数据库工作:

    创建语言表'语言':

    领域:

    language_id(主要和自动递增)

    LANGUAGE_NAME

    created_at

    由...制作

    的updated_at

    updated_by

    在数据库'content'中创建一个表:

    领域:

    content_id(主要和自动递增)

    main_content

    header_content

    footer_content

    leftsidebar_content

    rightsidebar_content

    language_id(外键:引用语言表)

    created_at

    由...制作

    的updated_at

    updated_by

    前端工作:

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

    $ _SESSION [ '语言'] = 1;

    现在根据存储在会话中的语言ID从数据库表'content'中获取数据 .

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

  • -1

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

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

  • 101

    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

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

  • 4

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

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

相关问题