【问题标题】:PHP simple translation approach - your opinionPHP简单翻译方法-你的意见
【发布时间】:2010-12-30 18:39:43
【问题描述】:

我正在做一个 PHP 网站,没有使用任何框架。我需要该网站有多种语言版本,我正在阅读它,它似乎有点令人困惑。有几种解决方案,但似乎都依赖于特定的框架。

您对使用如下所示的简单翻译功能有何看法?

我的意思是,我想知道使用这样的代码有什么缺点。 在这里(这只是一个简单且不完整的示例):

class Translator{

    private $translations;

    public function __construct(){
        $this->translations = array(
            'Inbox'  => array(
                'en' => 'Inbox',
                'fr' => 'the french word for this'
            ),
            'Messages' => array(
                'en' => 'Messages',
                'fr' => 'the french word for this'
            )
            //And so on...
        );
    }

    public function translate($word,$lang){
        echo $this->translations[$word][$lang];
    }
}

【问题讨论】:

  • 如果您可以使用 gettext(您应该这样做),请查看 PHP-I18N 以获得完整的介绍和帮助类。

标签: php internationalization translation


【解决方案1】:

看起来还不错。这个我见过很多次了。

但是,我会在每种语言的一个文件中分隔不同的字符串。至少,或者如果文件变大,每种语言每个模块一个文件。

然后,您的翻译类可以在每次使用新语言时加载和缓存语言文件(如果您不依赖任何其他缓存系统)。

我的意思的一个小例子

class Translator {
    private $lang = array();
    private function findString($str,$lang) {
        if (array_key_exists($str, $this->lang[$lang])) {
            return $this->lang[$lang][$str];
        }
        return $str;
    }
    private function splitStrings($str) {
        return explode('=',trim($str));
    }
    public function __($str,$lang) {
        if (!array_key_exists($lang, $this->lang)) {
            if (file_exists($lang.'.txt')) {
                $strings = array_map(array($this,'splitStrings'),file($lang.'.txt'));
                foreach ($strings as $k => $v) {
                    $this->lang[$lang][$v[0]] = $v[1];
                }
                return $this->findString($str, $lang);
            }
            else {
                return $str;
            }
        }
        else {
            return $this->findString($str, $lang);
        }
    }
}

这将查找以具有此类条目的语言命名的 .txt 文件

Foo=FOO
酒吧=酒吧

如果找不到任何翻译,它总是回退到原始字符串。

这是一个非常简单的例子。但是,如果您不需要更大的框架,我认为自己这样做并没有错。

要以更简单的方式使用它,您可以随时执行此操作并创建一个名为“EN_Example.txt”的文件

class Example extends Translator {
    private $lang = 'EN';
    private $package = 'Example';
    public function __($str) {
        return parent::__($str, $this->lang . '_' . $this->package);
    }
}

有时您希望翻译包含变量的字符串。一种这样的方法是我发现它很简单,可以不时使用。

// Translate string "Fox=FOX %s %s"
$e = new Example();
// Translated string with substituted arguments
$s = printf($e->__('Fox'),'arg 1','arg 2');

为了进一步集成变量替换,可以像这样将 printf 功能放在 __() 函数中

public function __() {
    if (func_num_args() < 1) {
        return false;
    }
    $args = func_get_args();
    $str = array_shift($args);
    if (count($args)) {
        return vsprintf(parent::__($str, $this->lang . '_' . $this->package),$args);
    }
    else {
        return parent::__($str, $this->lang . '_' . $this->package);
    }
}

【讨论】:

    【解决方案2】:

    您似乎没有考虑到一些事情:

    • 您只是在翻译单个单词吗?不同语言之间的句子结构和句法有何不同?
    • 如果一个单词或句子还没有被翻译成一种语言,你会怎么做?
    • 您的翻译是否支持变量?句子中单词的顺序在不同的语言中可能有所不同,如果您有一个变量,那么简单地将单词拆分到句子周围通常是不够的。

    我已经使用过两种解决方案,并且会推荐用于 PHP:

    【讨论】:

    • 这似乎不是“翻译”场景,而更像是“替代语言实现包”场景,此时它只是后端上的一堆预定字符串。语法问题应该是无关紧要的。
    【解决方案3】:

    为此使用类或函数的优势在于,您可以随着项目的增长更改语言的存储。如果您只有几个字符串,那么您的解决方案绝对没有问题。

    如果您有很多字符串,则在所有页面加载时加载语言数组可能需要时间、内存和硬盘资源。然后你可能想把它分成不同的文件,或者甚至可能使用数据库后端。如果使用 i 数据库,请考虑使用缓存(例如 memcached),这样您就不需要在每次页面加载时查询数据库数百次。

    您也可以查看gettext,它使用了非常快的预编译语言文件。

    【讨论】:

      【解决方案4】:

      我认为对每种语言简单地使用一个包含可能会更容易,其内容可能只是一个定义列表。

      通过这样做,您可以避免包含所有语言数据的开销和定期调用“翻译”函数的开销。

      再说一次,这种方法会限制未来的灵活性。 (但这可能不是一个因素。)

      【讨论】:

      • 没想到!这似乎更简单!这似乎解决了前面提到的问题......谢谢
      • 这绝不是一个完美的解决方案,但它可能尽可能简单。请记住 - UTF8 是您的朋友。 :-)
      • 定义是你的敌人... :-)
      • 我以前使用定义完成翻译,看看 osCommerce 或其任何衍生产品。这个系统很快就崩溃了。
      • @Peter Lindqvist。呃——你说了'o'这个词。 (我对过去的记忆不寒而栗。)正如我所说,这绝不是一个完美的解决方案。然而,它很简单,对于一个小项目可能就足够了。
      【解决方案5】:

      不使用框架也没关系。我看到你的函数的唯一问题是它将大量数据加载到内存中。我建议为每种语言使用数组,这样您只需要加载正在使用的语言。

      【讨论】:

      • 很抱歉我缺乏经验,但我如何管理一个班级的不同阵列?谢谢
      • @Sandro,您可能希望在语言上有一个 switch case 或 if-else 块,以便为给定语言的语言文件运行 include。这样您就可以单独存储语言文件。
      【解决方案6】:

      使用常量(定义)是一种不好的做法吗?

      我就是这样设置的。只是为了支持多语言。

      我有一个葡萄牙语文件和一个英文文件,其中包含:

      define('CONST','Meaning');
      

      也许这有点占用内存,但我可以从任何我想要的地方访问 :)

      我可能会改用 oop 方法,但现在我有这个。

      【讨论】:

      • 是的,这是一个不好的方法。尝试为单词 TRUE 或 GROUPING 定义翻译。这些已经存在。您想防止名称冲突。一种选择是为所有常量添加前缀,但您永远无法确定第三方库没有定义相同的常量。根据经验:在全局范围内尽可能少地放置。将属于一起的东西封装起来。见php.net/manual/en/reserved.constants.phpphp.net/manual/en/userlandnaming.php
      【解决方案7】:

      很久以前,当我遇到这样的问题(但对于一个非常小的网站,只有几页)时,我创建了一个名为 langpack.php 的文件,并且我网站上的任何文本字符串都必须通过该文件运行.现在,我将使用类似的方法,但拆分为多个文件。

      OOP 方法示例

      langpack.php

      abstract class langpack {
          public static $language = array();
      
          public static function get($n) {
              return isset(self::$language[$n]) ? self::$language[$n] : null;
          }
      }
      

      english.php

      final class English extends langpack {
          public static $language = array(
              'inbox' => 'Inbox',
              'messages' => 'Messages',
              'downloadError' => 'There was an error downloading your files',
          );
      }
      

      法语.php

      final class French extends langpack {
          public static $language = array(
              'inbox' => 'Inbioux',
              'messages' => 'Omelette du Fromage',
              'downloadError' => 'C\'est la vie',
          );
      }
      

      你应该从那里得到这个想法。在配置文件中实现autoloader,然后加载语言应该是您可以从会话、URL 或其他任何内容轻松完成的事情,通过使用 PHP 的变量性质和类实例化,如下所示:

      $langpack = new $_SESSION['language'];
      echo $langpack::get('inbox');
      

      当然,所有这些都可以用简单的数组来完成,并以命令式的方式访问(通过$GLOBALS 处理绝对引用)以减少一些开销,甚至可能使处理这一切的机制更多一些透明,但是,嘿,那不会很OO吧?

      【讨论】:

      • __get 是性能杀手!!我曾经为我的配置类做类似的事情,但发现只通过 $data 数组并公开所有变量会快得多
      • 还有!来自手册注意:属性不能声明为final,只有类和方法可以声明为final。
      • 哈,我在原版中犯了两个错误;纠正两者,并删除魔术功能。此外,性能杀手有点夸大其词,除非您在一个以单个 MHz 为单位测量性能的盒子上运行。
      • 确实是性能杀手。我分析了我做的一些网站,发现使用 __get 方法的访问时间与正常访问相比非常非常高
      • 这是古老的历史,没错,但是:“允许通过将成员变量声明为 public 来直接访问它们,但不鼓励使用访问器方法(set & get)。”来自framework.zend.com/manual/en/coding-standard.coding-style.html——只是说'
      【解决方案8】:

      也可以使用Symfony translation component,不需要框架,composer 有助于处理依赖关系:

      composer install --prefer-dist "symfony/translation":"@stable"

      【讨论】:

        【解决方案9】:

        如果您出于其他原因不使用任何框架,我认为没关系。当您不能/不想使用更结构化的翻译框架时,我们与您的情况相同:

        我们在一个小型 PHP 项目中工作,正在寻找一些简单的翻译机制。我们使用了与您类似的数组方法,但每种语言文本都有单独的文件。我们放置了一个小组件以保持尽可能清洁。

        如果你想看一看,我们在https://github.com/BrainCrumbz/simple-php-translate 上分享了它。请随时改进!

        【讨论】:

          【解决方案10】:

          我会简单地使用带有控制器和语言输入的函数,例如:

          function getLanguageFile($controller, $lang){
           // security: only allow letters a-z in both strings
           $controller = preg_replace('/([^a-z]*)/', '', $controller);
           $lang = preg_replace('/([^a-z]*)/', '', $lang);
           // return language details if present on disk
           if (is_file('lang/'.$controller.'/'.$lang.'.json')){
            return json_decode(file_get_contents('lang/'.$controller.'/'.$lang.'.json'));
           }
           return false;
          }
          

          如果控制器是索引并且语言是 en,您只需将 json 格式的字符串放在 lang/index/en.json 中。 您可以为依赖项添加一个函数(例如,您希望在访问另一个控制器时加载索引控制器值)您所要做的就是合并结果。 您也可以简单地将 php 文件包含在数组中,然后返回数组,但我建议您将这些翻译拆分为更大的项目。如果你的项目不大,你的功能绝对没问题。

          【讨论】:

            猜你喜欢
            • 2012-02-25
            • 1970-01-01
            • 1970-01-01
            • 2023-03-31
            • 2011-12-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多