是否有人成功地在Symfony 3上翻译Sonata Admin实体(实际上我使用的是3.3)。
我尝试了不同的解决方案,但没有一个真正奏效。 使用gedmo转换时,主要问题是为数据库上的不同语言保存了翻译,但是在管理员(列表结束表单)中,Sonata包仅显示默认的语言环境转换,尽管单击了不同的标记/翻译/选用。
我也尝试过使用KNP tarnslation包,并使用A2lix翻译,但是这两个有同样的问题:当你设置(在管理类中)一个字段为" sortable"然后在记录列表中,当你尝试按字段排序时,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
}
}
请帮忙!
答案 0 :(得分:2)
有同样的问题。其实我有两个问题:
实体未实现Sonata \ TranslationBundle \ Model \ Gedmo \ TranslatableInterface。即使稍后添加,也需要清除symfony缓存。
我发现在Sonata Admin安装或奏鸣曲包之一中我添加了DoctrineExtensionListener,如shown here。在听众中,Gedmo启动了当前的语言环境,当然它对SonataTranslateBundle一无所知。
所以我建议从请求参数,sonata翻译语言环境和gedmo启动的语言环境中转储(调试)symfony语言环境,并检查这些是否已同步。
最后我将语言环境设置为stored in user session并更新了DoctrineExtensionListener以使用会话中的语言环境。