mnapoli/doctrine-translated

Doctrine 的翻译字符串

0.2.0 2014-05-22 08:20 UTC

This package is auto-updated.

Last update: 2024-09-19 20:40:18 UTC


README

Build Status Coverage Status Scrutinizer Code Quality Total Downloads

这个库是 Doctrine 可翻译扩展的替代品。

基本思路是将“神奇地管理同一实体的多个版本”转换为“我的实体字段是一个包含多个翻译的对象”。

它旨在行为极其简单和明确,以便它可以可靠、易于维护、易于扩展和理解。目标是少做而多得到。

要求

此库需要 PHP 5.4Doctrine 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