add translations in CRUD

This commit is contained in:
Simon Vieille 2021-05-13 17:49:19 +02:00
parent 70759a2d50
commit 6a9830a494
20 changed files with 10208 additions and 15 deletions

View File

@ -13,6 +13,7 @@ $pagination-active-bg: #343a40;
@import "~choices.js/src/styles/choices.scss";
@import "~bootstrap/scss/bootstrap.scss";
@import "~@fortawesome/fontawesome-free/css/all.css";
@import "~flag-icon-css/sass/flag-icon.scss";
@for $i from 1 through 100 {
.miw-#{$i*5} {
@ -20,6 +21,10 @@ $pagination-active-bg: #343a40;
}
}
.flag-icon-en {
background-image: url(~flag-icon-css/flags/4x3/gb.svg);
}
body {
overflow-x: hidden;
}

View File

@ -14,6 +14,7 @@
"doctrine/doctrine-bundle": "^2.2",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.8",
"knplabs/doctrine-behaviors": "^2.2",
"knplabs/knp-paginator-bundle": "^5.4",
"phpdocumentor/reflection-docblock": "^5.2",
"scheb/2fa-google-authenticator": "^5.7",

View File

@ -19,4 +19,5 @@ return [
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
App\Core\Bundle\CoreBundle::class => ['all' => true],
App\Bundle\AppBundle::class => ['all' => true],
Knp\DoctrineBehaviors\DoctrineBehaviorsBundle::class => ['all' => true],
];

View File

@ -46,6 +46,9 @@ abstract class CrudController extends AdminController
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('new'), $entity);
if ($request->isMethod('POST')) {
@ -86,6 +89,9 @@ abstract class CrudController extends AdminController
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('edit'), $entity);
if ($request->isMethod('POST')) {
@ -181,4 +187,15 @@ abstract class CrudController extends AdminController
$session->set($form->getName(), $filters);
}
}
protected function prepareEntity(EntityInterface $entity)
{
$configuration = $this->getConfiguration();
if ($configuration->isI18n()) {
foreach ($configuration->getLocales() as $locale) {
$entity->addTranslation($entity->translate($locale, false));
}
}
}
}

View File

@ -19,6 +19,8 @@ class CrudConfiguration
protected array $viewDatas = [];
protected array $fields = [];
protected array $maxPerPage = [];
protected array $locales = [];
protected ?string $defaultLocale = null;
protected static $self;
@ -198,4 +200,29 @@ class CrudConfiguration
{
return $this->maxPerPage[$page] ?? $default;
}
/* -- */
public function setI18n(array $locales, string $defaultLocale): self
{
$this->locales = $locales;
$this->defaultLocale = $defaultLocale;
return $this;
}
public function getLocales(): array
{
return $this->locales;
}
public function getDefaultLocale(): ?string
{
return $this->defaultLocale;
}
public function isI18n(): bool
{
return !empty($this->locales);
}
}

View File

@ -2,8 +2,8 @@
namespace App\Core\Crud\Field;
use App\Core\Crud\Exception\CrudConfigurationException;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Twig\Environment;
@ -14,11 +14,11 @@ use Twig\Environment;
*/
abstract class Field
{
public function buildView(Environment $twig, $entity, array $options)
public function buildView(Environment $twig, $entity, array $options, ?string $locale = null)
{
return $twig->render($this->getView($options), [
'entity' => $entity,
'value' => $this->getValue($entity, $options),
'value' => $this->getValue($entity, $options, $locale),
'options' => $options,
]);
}
@ -43,12 +43,20 @@ abstract class Field
return $resolver;
}
protected function getValue($entity, array $options)
protected function getValue($entity, array $options, ?string $locale = null)
{
if (null !== $options['property']) {
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()->getPropertyAccessor();
$value = $propertyAccessor->getValue($entity, $options['property']);
try {
$value = $propertyAccessor->getValue($entity, $options['property']);
} catch (NoSuchPropertyException $e) {
if (null !== $locale) {
$value = $propertyAccessor->getValue($entity->translate($locale), $options['property']);
} else {
throw $e;
}
}
} elseif (null !== $options['property_builder']) {
$value = call_user_func($options['property_builder'], $entity, $options);
} else {

View File

@ -0,0 +1,20 @@
<?php
namespace App\Core\Manager;
use App\Core\Entity\EntityInterface;
/**
* class TranslatableEntityManager.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class TranslatableEntityManager extends EntityManager
{
protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity);
$entity->mergeNewTranslations();
$this->flush();
}
}

View File

@ -1,6 +1,12 @@
<div class="row">
<div class="col-md-12">
{{ form_widget(form) }}
{% for item in form %}
{% if configuration.isI18n and item.vars.name == 'translations' %}
{{ include(configuration.view('form_translations', '@Core/admin/crud/_form_translations.html.twig')) }}
{% else %}
{{ include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) }}
{% endif %}
{% endfor %}
</div>
</div>

View File

@ -0,0 +1,33 @@
{% set doRender = false %}
{% set render %}
<ul class="nav nav-pills mt-3 mb-3 p-0">
{% for locale in configuration.locales %}
<li class="nav-item">
<a class="nav-link {% if loop.first %}active{% endif %}" data-toggle="tab" href="#form-locale-{{ loop.index }}">
<span class="flag-icon flag-icon-{{ locale }}"></span>
{{ locale|upper|trans }}
</a>
</li>
{% endfor %}
</ul>
<div class="tab-content">
{% for locale in configuration.locales %}
<div class="tab-pane {% if loop.first %}show active{% endif %}" id="form-locale-{{ loop.index }}">
{% for item in item.children[locale] %}
{% if not item.isRendered %}
{% set doRender = true %}
{% endif %}
{{ include(configuration.view('form_widget', '@Core/admin/crud/_form_widget.html.twig')) }}
{% endfor %}
</div>
{% endfor %}
</div>
{% endset %}
{% if doRender %}
{{ render|raw }}
{% endif %}

View File

@ -0,0 +1,3 @@
{% if not item.isRendered %}
{{ form_row(item) }}
{% endif %}

View File

@ -70,7 +70,7 @@
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include(configuration.view('editForm', '@Core/admin/crud/_form.html.twig')) }}
{{ include(configuration.view('form', '@Core/admin/crud/_form.html.twig')) }}
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<img {% for k, v in options.image_attr %}{{ k }}="{{ v }}"{% endfor %} src="{{ asset(value) }}">

View File

@ -65,7 +65,15 @@
<tbody>
{% for item in pager %}
{% block list_item %}
<tr data-dblclick="">
{%- set dbClick %}
{% if configuration.action('index', 'show', true) %}
{{ path(configuration.pageRoute('show'), {entity: item.id}) }}
{% elseif configuration.action('index', 'edit', true) %}
{{ path(configuration.pageRoute('edit'), {entity: item.id}) }}
{% endif %}
{% endset -%}
<tr data-dblclick="{{ dbClick }}">
{% for config in configuration.fields('index') %}
{% set attr = config.options.attr is defined ? config.options.attr : [] %}
{% set action = config.options.action is defined ? config.options.action : null %}
@ -73,14 +81,14 @@
<td {% for key, value in attr %}{{ key }}="{{ value }}"{% endfor %}>
{% if action == 'show' %}
<a href="{{ path(configuration.pageRoute('show'), {entity: item.id}) }}">
{{ render_field(item, config) }}
{{ render_field(item, config, configuration.defaultLocale) }}
</a>
{% elseif action == 'edit' %}
<a href="{{ path(configuration.pageRoute('edit'), {entity: item.id}) }}">
{{ render_field(item, config) }}
{{ render_field(item, config, configuration.defaultLocale) }}
</a>
{% else %}
{{ render_field(item, config) }}
{{ render_field(item, config, configuration.defaultLocale) }}
{% endif %}
</td>
{% endfor %}

View File

@ -36,7 +36,7 @@
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include(configuration.view('newForm', '@Core/admin/crud/_form.html.twig')) }}
{{ include(configuration.view('form', '@Core/admin/crud/_form.html.twig')) }}
</div>
</div>
</div>

View File

@ -37,7 +37,7 @@
{% block show %}
<div class="row">
<div class="col-md-12">
{{ include(configuration.view('showEntity', '@Core/admin/crud/_show.html.twig')) }}
{{ include(configuration.view('show_entity', '@Core/admin/crud/_show.html.twig')) }}
</div>
</div>
{% endblock %}

View File

@ -33,12 +33,12 @@ class CrudExtension extends AbstractExtension
];
}
public function renderField($entity, array $config): string
public function renderField($entity, array $config, ?string $locale = null): string
{
$field = $config['field'];
$instance = new $field();
$resolver = $instance->configureOptions(new OptionsResolver());
return $instance->buildView($this->twig, $entity, $resolver->resolve($config['options']));
return $instance->buildView($this->twig, $entity, $resolver->resolve($config['options']), $locale);
}
}

10042
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
"@fortawesome/fontawesome-free": "^5.11.2",
"bootstrap": "^4.3.1",
"choices.js": "^9.0.1",
"flag-icon-css": "^3.5.0",
"jquery": "^3.6.0",
"popper.js": "^1.16.0",
"qrcodejs": "^1.0.0",

View File

@ -11,6 +11,9 @@
"bjeavons/zxcvbn-php": {
"version": "1.2.0"
},
"brick/math": {
"version": "0.9.2"
},
"cocur/slugify": {
"version": "v4.0.0"
},
@ -111,6 +114,9 @@
"khanamiryan/qrcode-detector-decoder": {
"version": "1.0.4"
},
"knplabs/doctrine-behaviors": {
"version": "2.2.0"
},
"knplabs/knp-components": {
"version": "v3.0.1"
},
@ -132,6 +138,9 @@
"myclabs/php-enum": {
"version": "1.8.0"
},
"nette/utils": {
"version": "v3.2.2"
},
"nikic/php-parser": {
"version": "v4.10.4"
},
@ -162,6 +171,12 @@
"psr/log": {
"version": "1.1.3"
},
"ramsey/collection": {
"version": "1.1.3"
},
"ramsey/uuid": {
"version": "4.1.1"
},
"scheb/2fa-bundle": {
"version": "5.0",
"recipe": {

View File

@ -2747,6 +2747,11 @@ find-up@^4.0.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
flag-icon-css@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/flag-icon-css/-/flag-icon-css-3.5.0.tgz#430747d5cb91e60babf85494de99173c16dc7cf2"
integrity sha512-pgJnJLrtb0tcDgU1fzGaQXmR8h++nXvILJ+r5SmOXaaL/2pocunQo2a8TAXhjQnBpRLPtZ1KCz/TYpqeNuE2ew==
follow-redirects@^1.0.0:
version "1.13.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"