基于文件的翻译对我不起作用,因为客户需要更改文本。
所以我正在考虑实现这个接口来从数据库中获取数据并将结果缓存在 APC 缓存中。这是一个好的解决方案吗?
基于文件的翻译对我不起作用,因为客户需要更改文本。
所以我正在考虑实现这个接口来从数据库中获取数据并将结果缓存在 APC 缓存中。这是一个好的解决方案吗?
这可能是您正在寻找的:
介绍
这篇文章解释了如何在 Symfony 2 中使用数据库作为翻译存储。使用数据库提供翻译在 Symfony 2 中很容易做到,但不幸的是,它实际上并没有在 Symfony 2 网站上解释。
创建语言实体
首先,我们必须为语言管理创建数据库实体。就我而言,我创建了三个实体: Language 实体包含所有可用的语言(如法语、英语、德语)。
第二个实体名为LanguageToken。它代表所有可用的语言标记。令牌实体表示 xliff 文件的源标记。每个可用的可翻译文本都是一个标记。例如,我使用home_page作为标记,它在法语中被翻译为Page principale ,在英语中被翻译为Home page。
最后一个实体是LanguageTranslation实体:它包含特定语言的标记的翻译。在下面的示例中,Page principale是语言法语和令牌home_page的LanguageTranslation实体。
这是非常低效的,但是翻译被 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类需要拥有来自LanguageTranslationRepository ( translationRepository 成员)的所有翻译。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()); } }
这个解决方案不是最漂亮的(如果我找到更好的解决方案,我会更新这篇文章)但它正在工作^^
善于交际,分享!
看看Doctrine 2的Translatable 行为扩展。StofDoctrineExtensionsBundle将它与 Symfony 集成。
您可能想使用 PDO 连接查看此 Loader + Resource:https ://gist.github.com/3315472
然后你只需要让它感知缓存,比如在两者之间添加一个 memcache、apc、..。如果是这样,您可以禁用翻译器本身的文件缓存。