mnapoli / doctrine-translated
Doctrine 的翻译字符串
Requires
- php: >=5.4.0
- doctrine/orm: 2.5.*@dev
This package is auto-updated.
Last update: 2024-09-19 20:40:18 UTC
README
这个库是 Doctrine 可翻译扩展的替代品。
基本思路是将“神奇地管理同一实体的多个版本”转换为“我的实体字段是一个包含多个翻译的对象”。
它旨在行为极其简单和明确,以便它可以可靠、易于维护、易于扩展和理解。目标是少做而多得到。
要求
此库需要 PHP 5.4 和 Doctrine 2.5!
工作原理
该库依赖于 Doctrine 2.5 的新主要功能:内嵌对象。一个内嵌对象将看到其属性被插入使用它的实体中。
示例
namespace Acme\Model; /** * @Entity */ class Product { /** * @var TranslatedString * @Embedded(class = "Acme\Model\TranslatedString") */ protected $name; public function __construct() { $this->name = new TranslatedString(); } public function getName() { return $this->name; } }
如果您使用 YAML 而不是注解
Acme\Model\Product: type: entity embedded: name: class: Acme\Model\TranslatedString
通过扩展 Mnapoli\Translated\AbstractTranslatedString
,您定义 TranslatedString
。这样,您可以定义要支持的语言。此类可以在您的应用程序的任何地方重复使用,因此您只需定义一次。
namespace Acme\Model; /** * @Embeddable */ class TranslatedString extends \Mnapoli\Translated\AbstractTranslatedString { /** * @Column(type = "string", nullable=true) */ public $en; /** * @Column(type = "string", nullable=true) */ public $fr; }
如你所见,属性必须是公共的。
以下是相同的 YAML 映射
Acme\Model\TranslatedString: type: embeddable fields: en: type: string nullable: true fr: type: string nullable: true
然后您可以开始翻译该字段
$product = new Product(); $product->getName()->en = 'Some english here'; $product->getName()->fr = 'Un peu de français là'; echo $product->getName()->en;
通常在您的应用程序中,您不希望在读取或设置值时硬编码 "en" 或 "fr"。这是因为当前的区域设置会随着请求而变化。
这就是为什么这个库提供了辅助工具来简化它,同时还有 Translator
对象。
示例
// The default locale is "en" (you can provide a locale like "en_US" too, it will be parsed) $translator = \Mnapoli\Translated\Translator('en'); // If a user is logged in, we can set the locale to the user's one $translator->setLanguage('fr'); $str = new TranslatedString(); $str->en = 'foo'; $str->fr = 'bar'; // No need to manipulate the locale here echo $translator->get($str); // foo
当前集成
- Twig
{{ product.name|translate }}
配置步骤非常简单
$extension = new \Mnapoli\Translated\Integration\Twig\TranslatedTwigExtension($translator); $twig->addExtension($extension);
- Symfony 2
提供了 Mnapoli\Translated\Integration\Symfony2\TranslatedBundle
。您需要在 AppKernel.php
中注册捆绑包
class AppKernel extends Kernel { public function registerBundles() { $bundles = [ // ... new \Mnapoli\Translated\Integration\Symfony2\TranslatedBundle(), ]; // ...
然后在您的 app/config/config.yml
translated: default_locale: %locale%
TranslatedBundle 将自动监听请求的区域设置并相应地配置 Translator
。
这意味着您不需要做任何事情:只需使用 Translator,它将使用请求的区域设置来翻译内容。
如果当前的区域设置没有存储在请求中,您需要手动设置事件监听器。以下是一个使用会话的基本示例
class LocaleListener { private $translator; private $session; public function __construct(Translator $translator) { $this->translator = $translator; } public function setSession(Session $session) { $this->session = $session; } public function onRequest(GetResponseEvent $event) { if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { return; } $locale = $request->getSession()->get('_locale'); if ($locale) { $this->translator->setLanguage($locale); } } public function onLogin(InteractiveLoginEvent $event) { $user = $event->getAuthenticationToken()->getUser(); $lang = $user->getLanguage(); if ($lang) { $this->session->set('_locale', $lang); } } }
当用户登录时,他的/她的区域设置存储在会话中。以下是配置
services: acme.locale.interactive_login_listener: class: Acme\UserBundle\EventListener\LocaleListener calls: - [ setSession, [@session] ] tags: - { name: kernel.event_listener, event: security.interactive_login, method: onLogin } acme.locale.kernel_request_listener: class: Acme\UserBundle\EventListener\LocaleListener calls: - [ setSession, [@session] ] tags: - { name: kernel.event_listener, event: kernel.request, method: onRequest }
- Zend Framework 1
// In your Bootstrap protected function _initViewHelpers() { $this->bootstrap('View'); // Create or get $translator (\Mnapoli\Translated\Translator) // Create the helper $helper = new Mnapoli\Translated\Integration\Zend1\TranslateZend1Helper($translator); // The view helper will be accessible through the name "translate" $this->getResource('view')->registerHelper($helper, 'translate'); }
然后您可以在视图中使用该助手
echo $this->translate($someTranslatedString);
注意:在 ZF1 中已经存在名为 translate
的视图助手。下面显示的示例将覆盖它。如果您不想覆盖它,可以使用不同的名称。
优点和缺点
使用该方法,您最终将在数据库中只有一个表
mysql> SELECT * FROM Product;
+----+---------+---------+
| id | name_en | name_fr |
+----+---------+---------+
| 1 | Hello | Salut |
+----+---------+---------+
这使得它在性能上非常好,以及其他原因
- 因为没有数据库往返,您总是得到所有的翻译
- 没有连接,这是一个完美的简单查询
- 独立的翻译(没有存储所有翻译的单个表)
- 没有索引问题(您可以添加想要的索引)
- 非常易于手动浏览/编辑数据库
然而,请注意缺点
- 如果您支持 100 种语言,您将得到巨大的表和内存中的大型对象
- 如果您添加了新语言,您需要更新您的数据库(虽然 Doctrine 可以自动完成此操作)
翻译器
您在上面看到了使用翻译器的一个基本示例。
以下是您可以对其执行的所有操作
// Get the translation for the current locale echo $translator->get($str); // Set the translation for the current locale $translator->set($str, 'Hello'); // Set the translation for several locales $translator->setMany($str, [ 'en' => 'Hello', 'fr' => 'Salut', ]);
从头创建新的翻译
$str = $translator->set(new TranslatedString(), 'Hello'); // Same as: $str = new TranslatedString(); $translator->set($str, 'Hello');
操作
有时您需要在模型中连接字符串,因此您不能使用翻译器(您可能也不想使用)。
您可以在翻译后的字符串上执行一些基本操作。
连接
$str1 = new TranslatedString(); $str1->en = 'Hello'; $str1->fr = 'Bonjour'; // $result is a TranslatedString $result = $str1->concat(' ', $user->getName()); // Will echo "Hello John" or "Bonjour John" according to the locale echo $translator->get($result);
您也可以从头创建字符串连接
$result = TranslatedString::join([ new TranslatedString('Hello', 'en'), '!' ]);
Implode
就像连接一样
$result = TranslatedString::implode(', ', [ new TranslatedString('foo', 'en'), 'bar' ]); // "foo, bar" echo $result->en;
未翻译的字符串
有时您应该提供或返回一个 TranslatedString
,但您有一个未翻译的字符串。例如
public function getParentLabel() { if ($this->parent === null) { return '-'; } return $this->parent->getLabel(); }
这里有一个问题:'-'
是一个简单的字符串,如果调用代码期望一个 TranslatedString
,则它将不起作用。
为此,您可以简单地创建一个“未翻译”的字符串
return TranslatedString::untranslated('-');
它对每种语言都具有相同的值(或翻译)。
回退
您可以在 Translator
上定义回退
$translator = new Translator('en', [ 'fr' => ['en'], // french fallbacks to english if not found 'es' => ['fr', 'en'], // spanish fallbacks to french, then english if not found ]);
如您所见,回退是可选的,并且可以有多个。
现在翻译器将使用这些回退
$str = new TranslatedString(); $str->en = 'Hello!'; // Will show nothing (no FR value) echo $str->fr; $translator->setLanguage('fr'); // Will show "Hello!" because the french falls back to english if not defined echo $translator->get($str);
您会注意到您也可以直接在 TranslatedString 对象上使用回退
// Nothing echo $str->fr; // Nothing echo $str->get('fr'); // Will show "Hello!" (the fallback is "en") echo $str->get('fr', ['en']);
Doctrine
文档正在编写中
关于持久化或检索实体没有变化。当您从数据库中加载实体时,所有翻译都将被加载。
然而,由于 Product::name
已不再是字符串,您不能简单地根据该字段进行筛选。您需要编写如下查询
$query = $em->createQuery(sprintf( "SELECT p FROM Product p WHERE p.name.%s = 'Hello'", $lang )); $products = $query->getResult();
同样适用于 ORDER BY
$query = $em->createQuery(sprintf( "SELECT p FROM Product p ORDER BY p.name.%s ASC", $lang )); $products = $query->getResult();
可以从 Translator
获取 $lang
(当前区域设置)。
我正在寻找使这更简单的方法,例如使用 DQL 函数(https://github.com/mnapoli/DoctrineTranslated/blob/master/src/Doctrine/TranslatedFunction.php)。请随意帮助,目前这遇到了瓶颈,因为 Doctrine 本身实例化了“函数”类,这阻止了使用依赖注入来注入当前的区域设置(或翻译器)。
在 Doctrine 上打开的当前问题:#991。