【发布时间】:2012-04-30 12:04:18
【问题描述】:
基于文件的翻译对我不起作用,因为客户需要更改文本。
所以我正在考虑实现this interface 以从数据库中获取数据并将结果缓存在 APC 缓存中。 这是一个好的解决方案吗?
【问题讨论】:
-
请注意,Symfony 将翻译缓存到文件中,而不管翻译的来源。 Symfony 在第一次运行时就这样做了。
标签: symfony translation
基于文件的翻译对我不起作用,因为客户需要更改文本。
所以我正在考虑实现this interface 以从数据库中获取数据并将结果缓存在 APC 缓存中。 这是一个好的解决方案吗?
【问题讨论】:
标签: symfony translation
这可能是您正在寻找的:
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_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 behavior extension。StofDoctrineExtensionsBundle 将它与 Symfony 集成。
【讨论】:
您可能希望使用 PDO 连接查看此加载器 + 资源:https://gist.github.com/3315472
然后你只需要让它感知缓存,比如在两者之间添加一个 memcache、apc、..。 如果是这样,您可以禁用 Translator 本身的文件缓存。
【讨论】: