2017-08-11 176 views
2

有人在Symfony 3上翻译了Sonata Admin实体(实际上我正在使用3.3)。Symfony 3上的Sonata管理实体翻译

我尝试了不同的解决方案,但都没有真正奏效。 使用gedmo翻译,主要问题是翻译保存为数据库中的不同语言,但是在管理员(也是列表结束表单)中,Sonata软件包仅显示缺省语言环境翻译,尽管不同的标志/翻译是点击/选用。

我也试过用KNP tarnslation包和A2lix翻译,但是这两个有相同的问题:当你设置(在管理员类中)一个字段为“可排序”,那么当你试图在记录列表中按照字段排序,Symfony抛出一个错误,因为翻译系统试图克隆与另一个领域不存在的关联!

无论如何,留在Gedmo的灵魂,主要问题是(因为我已经提到的问题,拆分A2lix解决方案)我不知道如何设置字段在管理类中可翻译(BlogPostAdmin.php ),因为简单地使用配置文件和实体和翻译类,似乎没有工作。正如已经说过的那样,问题在于翻译保存在数据库中,但不会显示在管理列表/表单中。

这里是我的配置和实体文件:

AppKernel.php

<?php 

use Symfony\Component\HttpKernel\Kernel; 
use Symfony\Component\Config\Loader\LoaderInterface; 

class AppKernel extends Kernel 
{ 
    public function registerBundles() 
    { 
     $bundles = [ 
      new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 
      new Symfony\Bundle\SecurityBundle\SecurityBundle(), 
      new Symfony\Bundle\TwigBundle\TwigBundle(), 
      new Symfony\Bundle\MonologBundle\MonologBundle(), 
      new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), 
      new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), 
      new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), 
      new AppBundle\AppBundle(), 
      /// These are the other bundles the SonataAdminBundle relies on 
      new Sonata\CoreBundle\SonataCoreBundle(), 
      new Sonata\BlockBundle\SonataBlockBundle(), 
      new Knp\Bundle\MenuBundle\KnpMenuBundle(), 
      new Sonata\TranslationBundle\SonataTranslationBundle(), 

      // And finally, the storage and SonataAdminBundle 
      new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(), 
      new Sonata\AdminBundle\SonataAdminBundle(), 

      // stof [used in Sonata translations] 
      new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(), 

      // assetic 
      new Symfony\Bundle\AsseticBundle\AsseticBundle(), 
     ]; 

     if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { 
      $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); 
      $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 
      $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); 

      if ('dev' === $this->getEnvironment()) { 
       $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); 
       $bundles[] = new Symfony\Bundle\WebServerBundle\WebServerBundle(); 
      } 
     } 

     return $bundles; 
    } 

    public function getRootDir() 
    { 
     return __DIR__; 
    } 

    public function getCacheDir() 
    { 
     return dirname(__DIR__).'/var/cache/'.$this->getEnvironment(); 
    } 

    public function getLogDir() 
    { 
     return dirname(__DIR__).'/var/logs'; 
    } 

    public function registerContainerConfiguration(LoaderInterface $loader) 
    { 
     $loader->load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml'); 
    } 
} 

config.yml

imports: 
    - { resource: parameters.yml } 
    - { resource: security.yml } 
    - { resource: services.yml } 

# Put parameters here that don't need to change on each machine where the app is deployed 
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 
parameters: 
    locale: it 

framework: 
    #esi: ~ 
    translator: { fallbacks: ['%locale%'] } 
    secret: '%secret%' 
    router: 
     resource: '%kernel.project_dir%/app/config/routing.yml' 
     strict_requirements: ~ 
    form: ~ 
    csrf_protection: ~ 
    validation: { enable_annotations: true } 
    #serializer: { enable_annotations: true } 
    templating: 
     engines: ['twig'] 
    default_locale: '%locale%' 
    trusted_hosts: ~ 
    session: 
     # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id 
     handler_id: session.handler.native_file 
     save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%' 
    fragments: ~ 
    http_method_override: true 
    assets: ~ 
    php_errors: 
     log: true 

# Twig Configuration 
twig: 
    debug: '%kernel.debug%' 
    strict_variables: '%kernel.debug%' 

# Doctrine Configuration 
doctrine: 
    dbal: 
     driver: pdo_mysql 
     host: '%database_host%' 
     port: '%database_port%' 
     dbname: '%database_name%' 
     user: '%database_user%' 
     password: '%database_password%' 
     charset: UTF8 
     # if using pdo_sqlite as your database driver: 
     # 1. add the path in parameters.yml 
     #  e.g. database_path: "%kernel.project_dir%/var/data/data.sqlite" 
     # 2. Uncomment database_path in parameters.yml.dist 
     # 3. Uncomment next line: 
     #path: '%database_path%' 

    orm: 
     auto_generate_proxy_classes: '%kernel.debug%' 
     naming_strategy: doctrine.orm.naming_strategy.underscore 
     auto_mapping: true 
#  mappings: 
#   # Doctrine extensions 
#   translatable: 
#    type: annotation 
#    alias: Gedmo 
#    prefix: Gedmo\Translatable\Entity 
#    dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity/MappedSuperclass" 

# Swiftmailer Configuration 
swiftmailer: 
    transport: '%mailer_transport%' 
    host: '%mailer_host%' 
    username: '%mailer_user%' 
    password: '%mailer_password%' 
    spool: { type: memory } 

sonata_block: 
    default_contexts: [cms] 
    blocks: 
     # enable the SonataAdminBundle block 
     sonata.admin.block.admin_list: 
      contexts: [admin] 

sonata_translation: 
    locales: [it, en] 
    default_locale: %locale% 
    # here enable the types you need 
    gedmo: 
     enabled: true 
# knplabs: 
#  enabled: true 
    #phpcr: 
    # enabled: true 

sonata_admin: 
    templates: 
     layout: admin/layout.html.twig 

assetic: 
    debug:   '%kernel.debug%' 
    use_controller: '%kernel.debug%' 
    filters: 
     cssrewrite: ~ 

#stof_doctrine_extensions: 
# #default_locale: %locale% 
# orm: 
#  default: 
#   sluggable: true 
#   timestampable: true 

services.yml

# Learn more about services, parameters and containers at 
# https://symfony.com/doc/current/service_container.html 
parameters: 
    locale: 'it' 
    locales: ['it', 'en'] 

services: 
    # default configuration for services in *this* file 
    _defaults: 
     # automatically injects dependencies in your services 
     autowire: true 
     # automatically registers your services as commands, event subscribers, etc. 
     autoconfigure: true 
     # this means you cannot fetch services directly from the container via $container->get() 
     # if you need to do this, you can override this setting on individual services 
     public: false 

    # makes classes in src/AppBundle available to be used as services 
    # this creates a service per class whose id is the fully-qualified class name 
    AppBundle\: 
     resource: '../../src/AppBundle/*' 
     # you can exclude directories or files 
     # but if a service is unused, it's removed anyway 
     exclude: '../../src/AppBundle/{Entity,Repository,Tests}' 

    # controllers are imported separately to make sure they're public 
    # and have a tag that allows actions to type-hint services 
    AppBundle\Controller\: 
     resource: '../../src/AppBundle/Controller' 
     public: true 
     tags: ['controller.service_arguments'] 

    # add more services, or override services that need manual wiring 
    # AppBundle\Service\ExampleService: 
    #  arguments: 
    #   $someArgument: 'some_value' 

    admin.category: 
      class: AppBundle\Admin\CategoryAdmin 
      arguments: [~, AppBundle\Entity\Category, ~] 
      tags: 
       - { name: sonata.admin, manager_type: orm, label: Category } 
      public: true 

    admin.blog_post: 
     class: AppBundle\Admin\BlogPostAdmin 
     arguments: [~, AppBundle\Entity\BlogPost, ~] 
     tags: 
      - { name: sonata.admin, manager_type: orm, label: Blog post } 
     public: true 


    # Doctrine Extension listeners to handle behaviors 
    gedmo.listener.translatable: 
     class: Gedmo\Translatable\TranslatableListener 
     tags: 
      - { name: doctrine.event_subscriber, connection: default } 
     calls: 
      #- [ setAnnotationReader, [ @annotation_reader ] ] 
      - [ setDefaultLocale, [ it ] ] 
      - [ setTranslationFallback, [ false ] ] 
      - [ setPersistDefaultLocaleTranslation, [ false ] ] 

BlogPost.php

<?php 

namespace AppBundle\Entity; 

use Doctrine\ORM\Mapping as ORM; 
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslatable; 
use Gedmo\Mapping\Annotation as Gedmo; 
use Sonata\TranslationBundle\Model\Gedmo\TranslatableInterface; 
use Doctrine\Common\Collections\ArrayCollection; 
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslation; 
use Sonata\TranslationBundle\Traits\Gedmo\PersonalTranslatableTrait; 

/** 
* BlogPost 
* 
* @ORM\Table(name="blog_post") 
* @ORM\Entity(repositoryClass="AppBundle\Repository\BlogPostRepository") 
* @Gedmo\TranslationEntity(class="AppBundle\Entity\Translations\BlogPostTr") 
* @ORM\HasLifecycleCallbacks 
*/ 

class BlogPost implements TranslatableInterface 
{ 
    use PersonalTranslatableTrait; 

    /** 
    * Post locale 
    * Used locale to override Translation listener's locale 
    * 
    * @Gedmo\Locale 
    */ 
    protected $locale; 

    /** 
    * @ORM\ManyToOne(targetEntity="Category", inversedBy="blogPosts") 
    */ 
    private $category; 

    public function setCategory(Category $category) 
    { 
     $this->category = $category; 
    } 

    public function getCategory() 
    { 
     return $this->category; 
    } 

    /** 
    * @var int 
    * 
    * @ORM\Column(name="id", type="integer") 
    * @ORM\Id 
    * @ORM\GeneratedValue(strategy="AUTO") 
    */ 
    private $id; 

    /** 
    * @var string 
    * 
    * @ORM\Column(name="title", type="string", length=255) 
    * @Gedmo\Translatable 
    */ 
    private $title; 

    /** 
    * @var string 
    * 
    * @ORM\Column(name="body", type="text") 
    * @Gedmo\Translatable 
    */ 
    private $body; 

    /** 
    * @var bool 
    * 
    * @ORM\Column(name="draft", type="boolean") 
    */ 
    private $draft = false; 


    /** 
    * Get id 
    * 
    * @return int 
    */ 
    public function getId() 
    { 
     return $this->id; 
    } 

    /** 
    * Set title 
    * 
    * @param string $title 
    * 
    * @return BlogPost 
    */ 
    public function setTitle($title) 
    { 
     $this->title = $title; 

     return $this; 
    } 

    /** 
    * Get title 
    * 
    * @return string 
    */ 
    public function getTitle() 
    { 
     return $this->title; 
    } 

    /** 
    * Set body 
    * 
    * @param string $body 
    * 
    * @return BlogPost 
    */ 
    public function setBody($body) 
    { 
     $this->body = $body; 

     return $this; 
    } 

    /** 
    * Get body 
    * 
    * @return string 
    */ 
    public function getBody() 
    { 
     return $this->body; 
    } 

    /** 
    * Set draft 
    * 
    * @param boolean $draft 
    * 
    * @return BlogPost 
    */ 
    public function setDraft($draft) 
    { 
     $this->draft = $draft; 

     return $this; 
    } 

    /** 
    * Get draft 
    * 
    * @return bool 
    */ 
    public function getDraft() 
    { 
     return $this->draft; 
    } 

    // TRANSLATION 
    /** 
    * @ORM\OneToMany(targetEntity="AppBundle\Entity\Translations\BlogPostTr", mappedBy="object", cascade={"persist", "remove"}) 
    */ 
    protected $translations; 


    public function __construct() 
    { 
     $this->translations = new ArrayCollection; 
    } 

    public function getTranslations() 
    { 
     return $this->translations; 
    } 

    public function addTranslation(AbstractPersonalTranslation $t) 
    { 
     $this->translations->add($t); 
     $t->setObject($this); 
    } 

    public function removeTranslation(AbstractPersonalTranslation $t) 
    { 
     $this->translations->removeElement($t); 
    } 

    public function setTranslations($translations) 
    { 
     $this->translations = $translations; 
    } 

    /** 
    * Sets translatable locale 
    * 
    * @param string $locale 
    */ 
    public function setTranslatableLocale($locale) 
    { 
     $this->locale = $locale; 
    } 
} 

BlogPostTr.php

<?php 
namespace AppBundle\Entity\Translations; 

use Doctrine\ORM\Mapping as ORM; 
use Sonata\TranslationBundle\Model\Gedmo\AbstractPersonalTranslation; 

/** 
* @ORM\Entity 
* @ORM\Table(name="blog_post_translation", 
*  uniqueConstraints={@ORM\UniqueConstraint(name="lookup_unique_idx", columns={ 
*   "locale", "object_id", "field" 
*  })} 
*) 
*/ 
class BlogPostTr extends AbstractPersonalTranslation 
{ 
    /** 
    * Convinient constructor 
    * 
    * @param string $locale 
    * @param string $field 
    * @param string $content 
    */ 
    public function __construct($locale = null, $field = null, $content = null) 
    { 
     $this->setLocale($locale); 
     $this->setField($field); 
     $this->setContent($content); 
    } 

    /** 
    * @ORM\ManyToOne(targetEntity="AppBundle\Entity\BlogPost", inversedBy="translations") 
    * @ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE") 
    */ 
    protected $object; 
} 

BlogPostAdmin.php

<?php 

namespace AppBundle\Admin; 

use Sonata\AdminBundle\Admin\AbstractAdmin; 
use Sonata\AdminBundle\Datagrid\ListMapper; 
use Sonata\AdminBundle\Form\FormMapper; 

class BlogPostAdmin extends AbstractAdmin 
{ 
    protected function configureFormFields(FormMapper $formMapper) 
    { 
     $formMapper 
      ->tab('Post') 
      ->with('Content', array('class' => 'col-md-9')) 
      ->add('title', 'text') 
//   ->add('title', 'translatable_field', array(
//    'allow_extra_fields' => true, 
//    'field' => 'title', 
//    'personal_translation' => 'AppBundle\Entity\Translations\BlogPostTr', 
//    'property_path' => 'translations', 
//   )) 
      ->add('body', 'textarea') 
      ->end() 
      ->end() 
      ->tab('Publishing options') 
      ->with('Meta data', array('class' => 'col-md-3')) 
      ->add('category', 'sonata_type_model', array(
       'class' => 'AppBundle\Entity\Category', 
       'property' => 'name', 
      )) 
      ->end() 
      ->end(); 
    } 





// protected function configureDatagridFilters(DatagridMapper $datagridMapper) 
// { 
//  $datagridMapper->add('title'); 
// } 

    protected function configureListFields(ListMapper $listMapper) 
    { 
     $listMapper->addIdentifier('title'); 
    } 

    public function toString($object) 
    { 
     return $object instanceof BlogPost 
      ? $object->getTitle() 
      : 'Blog Post'; // shown in the breadcrumb on the create view 
    } 
} 

请帮帮忙!

回答

1

有同样的问题。其实我有两个问题:

  1. 实体没有实现Sonata \ TranslationBundle \ Model \ Gedmo \ TranslatableInterface。即使稍后添加,symfony缓存也需要清除。

  2. 我发现,在奏鸣曲管理员安装或奏鸣曲包之一中,我添加了DoctrineExtensionListener,如shown here。听众里面Gedmo启动当前的语言环境,当然它不知道有关SonataTranslateBundle的任何信息。

因此,我建议转储从请求参数,奏鸣曲翻译的语言环境(调试)symfony的语言环境和gedmo发起的区域设置和检查,这些都是同步的。

最后,我设置了语言环境为stored in user session,并更新了DoctrineExtensionListener以使用会话中的语言环境。