【问题标题】:Translations and Symfony2 in database数据库中的翻译和 Symfony2
【发布时间】:2012-04-30 12:04:18
【问题描述】:

基于文件的翻译对我不起作用,因为客户需要更改文本。

所以我正在考虑实现this interface 以从数据库中获取数据并将结果缓存在 APC 缓存中。 这是一个好的解决方案吗?

【问题讨论】:

  • 请注意,Symfony 将翻译缓存到文件中,而不管翻译的来源。 Symfony 在第一次运行时就这样做了。

标签: symfony translation


【解决方案1】:

这可能是您正在寻找的:

Use a database as a translation provider in Symfony 2

简介

这篇文章解释了如何在 Symfony 2 中使用数据库作为翻译存储。使用数据库提供翻译在 Symfony 2 中很容易做到,但不幸的是,它实际上并没有在 Symfony 2 网站上解释。

创建语言实体

首先,我们必须为语言管理创建数据库实体。在我的例子中,我创建了三个实体:Language 实体包含所有可用的语言(如 法语、英语、德语)。

第二个实体名为LanguageToken。它代表所有可用的语言标记。 token 实体表示 xliff 文件的 source 标记。每个可用的可翻译文本都是一个标记。例如,我使用 home_page 作为标记,它在法语中被翻译为 Page principale,在英文中被翻译为 Home page

最后一个实体是 LanguageTranslation 实体:它包含特定语言的标记的翻译。在下面的示例中,Page principale 是语言 french 和标记 home_pageLanguageTranslation 实体。

效率相当低,但是翻译被 Symfony 2 缓存在一个文件中,最后在 Symfony 2 第一次执行时只使用一次(除非你删除 Symfony 2 的缓存文件)。

语言实体的代码在这里可见:

/**
 * @ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageRepository")
 */
class Language {

    /**
     * @ORM\Id 
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    private $id;

    /** @ORM\column(type="string", length=200) */
    private $locale;


    /** @ORM\column(type="string", length=200) */
    private $name;


    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

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

    public function setLocale($locale) {
        $this->locale = $locale;
    }
    public function getName() {
        return $this->name;
    }

    public function setName($name) {
        $this->name = $name;
    }
}

LanguageToken 实体的代码在这里可见:

/**
 * @ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTokenRepository")
 */
class LanguageToken {

    /**
     * @ORM\Id @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    private $id;

    /** @ORM\column(type="string", length=200, unique=true) */
    private $token;


    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getToken() {
        return $this->token;
    }

    public function setToken($token) {
        $this->token = $token;
    }
}

LanguageTranslation 实体的代码在这里可见:

/**
 * @ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTranslationRepository")
 */
class LanguageTranslation {

    /**
     * @ORM\Id @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    private $id;

    /** @ORM\column(type="string", length=200) */
    private $catalogue;


    /** @ORM\column(type="text") */
    private $translation;


    /**
     * @ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\Language", fetch="EAGER")
     */
    private $language;

    /**
     * @ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\LanguageToken", fetch="EAGER")
     */
    private $languageToken;


    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getCatalogue() {
        return $this->catalogue;
    }

    public function setCatalogue($catalogue) {
        $this->catalogue = $catalogue;
    }

    public function getTranslation() {
        return $this->translation;
    }

    public function setTranslation($translation) {
        $this->translation = $translation;
    }

    public function getLanguage() {
        return $this->language;
    }

    public function setLanguage($language) {
        $this->language = $language;
    }

    public function getLanguageToken() {
        return $this->languageToken;
    }

    public function setLanguageToken($languageToken) {
        $this->languageToken = $languageToken;
    }
}

实现一个加载器接口

第二步是创建一个实现Symfony\Component\Translation\Loader\LoaderInterface的类。对应的类如下所示:

class DBLoader implements LoaderInterface{
    private $transaltionRepository;
    private $languageRepository;

    /**
     * @param EntityManager $entityManager
     */
    public function __construct(EntityManager $entityManager){
        $this->transaltionRepository = $entityManager->getRepository("AppCommonBundle:LanguageTranslation");
        $this->languageRepository = $entityManager->getRepository("AppCommonBundle:Language");
    }

    function load($resource, $locale, $domain = 'messages'){
        //Load on the db for the specified local
        $language = $this->languageRepository->getLanguage($locale);

        $translations = $this->transaltionRepository->getTranslations($language, $domain);

        $catalogue = new MessageCatalogue($locale);

        /**@var $translation Frtrains\CommonbBundle\Entity\LanguageTranslation */
        foreach($translations as $translation){
            $catalogue->set($translation->getLanguageToken()->getToken(), $translation->getTranslation(), $domain);
        }

        return $catalogue;
    }
}

DBLoader 类需要有来自 LanguageTranslationRepositorytranslationRepository 成员)的所有翻译。 translationRepository 对象的 getTranslations($language, $domain) 方法在这里可见:

class LanguageTranslationRepository extends EntityRepository {

    /**
     * Return all translations for specified token
     * @param type $token
     * @param type $domain 
     */
    public function getTranslations($language, $catalogue = "messages"){
        $query = $this->getEntityManager()->createQuery("SELECT t FROM AppCommonBundle:LanguageTranslation t WHERE t.language = :language AND t.catalogue = :catalogue");
        $query->setParameter("language", $language);
        $query->setParameter("catalogue", $catalogue);

        return $query->getResult();
    }
    ...
}

DBLoader 类将由 Symfony 创建为服务,接收 EntityManager 作为构造函数参数。 load 方法的所有参数都允许您自定义翻译加载器接口的工作方式。

使用 DBLoader 创建 Symfony 服务

第三步是使用之前创建的类创建服务。添加到 config.yml 文件的代码在这里:

services:
    translation.loader.db:
        class: MyApp\CommonBundle\Services\DBLoader
        arguments: [@doctrine.orm.entity_manager]
        tags:
            - { name: translation.loader, alias: db}

transation.loader 标签指示 Symfony 为 db 别名使用这个翻译加载器。

创建假翻译文件

最后一步是为每个翻译创建一个 app/Resources/translations/messages.xx.db 文件(其中 xx = en、fr、de、...)。

我没有找到通知 Symfony 使用 DBLoader 作为默认翻译加载器的方法。我发现的唯一快速破解方法是创建一个 app/Resources/translations/messages.en.db 文件。 db 扩展对应于服务声明中使用的 db 别名。为网站上可用的每种语言创建相应的文件,例如 messages.fr.db 用于法语或 messages.de.db 用于德语。

当 Symfony 找到 messages.xx.db 文件时,他加载 translation.loader.db 来管理这个未知的扩展,然后加载 DBLoader 使用数据库内容提供翻译。

我也没有找到在数据库修改时正确清理翻译缓存的方法(必须清理缓存以强制 Symfony 重新创建它)。我实际使用的代码在这里可见:

/**
 * Remove language in every cache directories
 */
private function clearLanguageCache(){
    $cacheDir = __DIR__ . "/../../../../app/cache";

    $finder = new \Symfony\Component\Finder\Finder();

    //TODO quick hack...
    $finder->in(array($cacheDir . "/dev/translations", $cacheDir . "/prod/translations"))->files();

    foreach($finder as $file){
        unlink($file->getRealpath());
    }
}

这个解决方案不是最漂亮的(如果我找到更好的解决方案,我会更新这篇文章)但它正在工作^^

善于交际,分享!

【讨论】:

    【解决方案2】:

    看看 Doctrine 2 的 Translatable behavior extensionStofDoctrineExtensionsBundle 将它与 Symfony 集成。

    【讨论】:

    • 我看过了,但这是针对实体而不是随机文本!
    【解决方案3】:

    您可能希望使用 PDO 连接查看此加载器 + 资源:https://gist.github.com/3315472

    然后你只需要让它感知缓存,比如在两者之间添加一个 memcache、apc、..。 如果是这样,您可以禁用 Translator 本身的文件缓存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-01
      • 1970-01-01
      • 2012-02-22
      • 2021-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多