diff --git a/config/parameters.yaml b/config/parameters.yaml index e86072fe..77536b98 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -16,6 +16,20 @@ parameters: partdb.create_assembly_use_ipn_placeholder_in_name: '%env(bool:CREATE_ASSEMBLY_USE_IPN_PLACEHOLDER_IN_NAME)%' # Use an %%ipn%% placeholder in the name of an assembly. Placeholder is replaced with the ipn input while saving. + partdb.data_sources.synonyms: # Define your own synonyms for the given data sources + # Possible datasources: category, storagelocation, footprint, manufacturer, supplier, project, assembly + # Possible locales like the ones in 'partdb.locale_menu': en, de, it, fr, ru, ja, cs, da, zh, pl + #category: + #de: 'Bauteil Kategorien' + #en: 'Part categories' + #project: + #de: 'Geräte' + #en: 'Devices' + #assembly: + #de: 'Zusammengestellte Baugruppe' + #en: 'Combined assembly' + + ###################################################################################################################### # Users and Privacy ###################################################################################################################### diff --git a/config/services.yaml b/config/services.yaml index ff8ce2ec..8256daa1 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -198,6 +198,25 @@ services: $fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/' $tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/' + #################################################################################################################### + # Trees + #################################################################################################################### + App\Services\Trees\TreeViewGenerator: + arguments: + $dataSourceSynonyms: '%partdb.data_sources.synonyms%' + App\Services\Trees\ToolsTreeBuilder: + arguments: + $dataSourceSynonyms: '%partdb.data_sources.synonyms%' + + #################################################################################################################### + # Twig Extensions + #################################################################################################################### + + App\Twig\DataSourceNameExtension: + arguments: + $dataSourceSynonyms: '%partdb.data_sources.synonyms%' + tags: [ 'twig.extension' ] + #################################################################################################################### # Part info provider system #################################################################################################################### diff --git a/src/Controller/AssemblyController.php b/src/Controller/AssemblyController.php index a1ba7fa6..94e12964 100644 --- a/src/Controller/AssemblyController.php +++ b/src/Controller/AssemblyController.php @@ -29,6 +29,7 @@ use App\DataTables\Filters\AssemblyFilter; use App\Entity\AssemblySystem\Assembly; use App\Entity\AssemblySystem\AssemblyBOMEntry; use App\Entity\Parts\Part; +use App\Entity\UserSystem\User; use App\Exceptions\InvalidRegexException; use App\Form\AssemblySystem\AssemblyAddPartsType; use App\Form\AssemblySystem\AssemblyBuildType; diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index f16431b3..78b99696 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -50,8 +50,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class ToolsTreeBuilder { - public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security) - { + public function __construct( + protected TranslatorInterface $translator, + protected UrlGeneratorInterface $urlGenerator, + protected TagAwareCacheInterface $cache, + protected UserCacheKeyGenerator $keyGenerator, + protected Security $security, + protected ?array $dataSourceSynonyms = [], + ) { + $this->dataSourceSynonyms = $dataSourceSynonyms ?? []; } /** @@ -166,43 +173,43 @@ class ToolsTreeBuilder } if ($this->security->isGranted('read', new Category())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.categories'), + $this->getTranslatedDataSourceOrSynonym('category', 'tree.tools.edit.categories', $this->translator->getLocale()), $this->urlGenerator->generate('category_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-tags'); } if ($this->security->isGranted('read', new Project())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.projects'), + $this->getTranslatedDataSourceOrSynonym('project', 'tree.tools.edit.projects', $this->translator->getLocale()), $this->urlGenerator->generate('project_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-archive'); } if ($this->security->isGranted('read', new Assembly())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.assemblies'), + $this->getTranslatedDataSourceOrSynonym('assembly', 'tree.tools.edit.assemblies', $this->translator->getLocale()), $this->urlGenerator->generate('assembly_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-list'); } if ($this->security->isGranted('read', new Supplier())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.suppliers'), + $this->getTranslatedDataSourceOrSynonym('supplier', 'tree.tools.edit.suppliers', $this->translator->getLocale()), $this->urlGenerator->generate('supplier_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-truck'); } if ($this->security->isGranted('read', new Manufacturer())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.manufacturer'), + $this->getTranslatedDataSourceOrSynonym('manufacturer', 'tree.tools.edit.manufacturer', $this->translator->getLocale()), $this->urlGenerator->generate('manufacturer_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-industry'); } if ($this->security->isGranted('read', new StorageLocation())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.storelocation'), + $this->getTranslatedDataSourceOrSynonym('storagelocation', 'tree.tools.edit.storelocation', $this->translator->getLocale()), $this->urlGenerator->generate('store_location_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-cube'); } if ($this->security->isGranted('read', new Footprint())) { $nodes[] = (new TreeViewNode( - $this->translator->trans('tree.tools.edit.footprint'), + $this->getTranslatedDataSourceOrSynonym('footprint', 'tree.tools.edit.footprint', $this->translator->getLocale()), $this->urlGenerator->generate('footprint_new') ))->setIcon('fa-fw fa-treeview fa-solid fa-microchip'); } @@ -310,4 +317,22 @@ class ToolsTreeBuilder return $nodes; } + + protected function getTranslatedDataSourceOrSynonym(string $dataSource, string $translationKey, string $locale): string + { + $currentTranslation = $this->translator->trans($translationKey); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($this->dataSourceSynonyms[$dataSource][$locale])) { + $alternativeTranslation = $this->dataSourceSynonyms[$dataSource][$locale]; + + // Use alternative translation when it deviates from the standard translation + if ($alternativeTranslation !== $currentTranslation) { + return $alternativeTranslation; + } + } + + // Otherwise return the standard translation + return $currentTranslation; + } } diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index 3a097902..d5358bfa 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -68,9 +68,11 @@ class TreeViewGenerator protected TranslatorInterface $translator, private readonly UrlGeneratorInterface $router, private readonly SidebarSettings $sidebarSettings, + protected ?array $dataSourceSynonyms = [], ) { $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; + $this->dataSourceSynonyms = $dataSourceSynonyms ?? []; } /** @@ -226,14 +228,16 @@ class TreeViewGenerator protected function entityClassToRootNodeString(string $class): string { + $locale = $this->translator->getLocale(); + return match ($class) { - Category::class => $this->translator->trans('category.labelp'), - StorageLocation::class => $this->translator->trans('storelocation.labelp'), - Footprint::class => $this->translator->trans('footprint.labelp'), - Manufacturer::class => $this->translator->trans('manufacturer.labelp'), - Supplier::class => $this->translator->trans('supplier.labelp'), - Project::class => $this->translator->trans('project.labelp'), - Assembly::class => $this->translator->trans('assembly.labelp'), + Category::class => $this->getTranslatedOrSynonym('category', $locale), + StorageLocation::class => $this->getTranslatedOrSynonym('storelocation', $locale), + Footprint::class => $this->getTranslatedOrSynonym('footprint', $locale), + Manufacturer::class => $this->getTranslatedOrSynonym('manufacturer', $locale), + Supplier::class => $this->getTranslatedOrSynonym('supplier', $locale), + Project::class => $this->getTranslatedOrSynonym('project', $locale), + Assembly::class => $this->getTranslatedOrSynonym('assembly', $locale), default => $this->translator->trans('tree.root_node.text'), }; } @@ -290,4 +294,22 @@ class TreeViewGenerator return $repo->getGenericNodeTree($parent); //@phpstan-ignore-line }); } + + protected function getTranslatedOrSynonym(string $key, string $locale): string + { + $currentTranslation = $this->translator->trans($key . '.labelp'); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($this->dataSourceSynonyms[$key][$locale])) { + $alternativeTranslation = $this->dataSourceSynonyms[$key][$locale]; + + // Use alternative translation when it deviates from the standard translation + if ($alternativeTranslation !== $currentTranslation) { + return $alternativeTranslation; + } + } + + // Otherwise return the standard translation + return $currentTranslation; + } } diff --git a/src/Twig/DataSourceNameExtension.php b/src/Twig/DataSourceNameExtension.php new file mode 100644 index 00000000..1c02243f --- /dev/null +++ b/src/Twig/DataSourceNameExtension.php @@ -0,0 +1,42 @@ +translator = $translator; + $this->dataSourceSynonyms = $dataSourceSynonyms ?? []; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('get_data_source_name', [$this, 'getDataSourceName']), + ]; + } + + /** + * Based on the locale and data source names, gives the right synonym value back or the default translator value. + */ + public function getDataSourceName(string $dataSourceName, string $defaultKey): string + { + $locale = $this->translator->getLocale(); + + // Use alternative dataSource synonym (if available) + if (isset($this->dataSourceSynonyms[$dataSourceName][$locale])) { + return $this->dataSourceSynonyms[$dataSourceName][$locale]; + } + + // Otherwise return the standard translation + return $this->translator->trans($defaultKey); + } +} \ No newline at end of file diff --git a/templates/admin/assembly_admin.html.twig b/templates/admin/assembly_admin.html.twig index e6a90dc0..def4eeb2 100644 --- a/templates/admin/assembly_admin.html.twig +++ b/templates/admin/assembly_admin.html.twig @@ -3,7 +3,9 @@ {# @var entity App\Entity\AssemblySystem\Assembly #} {% block card_title %} - {% trans %}assembly.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('assembly', 'assembly.caption') %} + {% set translatedSource = 'assembly.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/category_admin.html.twig b/templates/admin/category_admin.html.twig index 5811640b..f1fe7663 100644 --- a/templates/admin/category_admin.html.twig +++ b/templates/admin/category_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}category.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('category', 'category.labelp') %} + {% set translatedSource = 'category.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_pills %} diff --git a/templates/admin/footprint_admin.html.twig b/templates/admin/footprint_admin.html.twig index a2c3e4af..a6acbe84 100644 --- a/templates/admin/footprint_admin.html.twig +++ b/templates/admin/footprint_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_admin.html.twig" %} {% block card_title %} - {% trans %}footprint.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('footprint', 'footprint.labelp') %} + {% set translatedSource = 'footprint.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block master_picture_block %} diff --git a/templates/admin/manufacturer_admin.html.twig b/templates/admin/manufacturer_admin.html.twig index 5db892c0..3ce9a124 100644 --- a/templates/admin/manufacturer_admin.html.twig +++ b/templates/admin/manufacturer_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}manufacturer.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('manufacturer', 'manufacturer.caption') %} + {% set translatedSource = 'manufacturer.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/project_admin.html.twig b/templates/admin/project_admin.html.twig index dcf8c64c..401be7cf 100644 --- a/templates/admin/project_admin.html.twig +++ b/templates/admin/project_admin.html.twig @@ -3,7 +3,9 @@ {# @var entity App\Entity\ProjectSystem\Project #} {% block card_title %} - {% trans %}project.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('project', 'project.caption') %} + {% set translatedSource = 'project.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block edit_title %} diff --git a/templates/admin/storelocation_admin.html.twig b/templates/admin/storelocation_admin.html.twig index c93339dc..1e60eeea 100644 --- a/templates/admin/storelocation_admin.html.twig +++ b/templates/admin/storelocation_admin.html.twig @@ -2,7 +2,9 @@ {% import "label_system/dropdown_macro.html.twig" as dropdown %} {% block card_title %} - {% trans %}storelocation.labelp{% endtrans %} + {% set dataSourceName = get_data_source_name('storagelocation', 'storelocation.labelp') %} + {% set translatedSource = 'storelocation.labelp'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_controls %} diff --git a/templates/admin/supplier_admin.html.twig b/templates/admin/supplier_admin.html.twig index ce38a5ca..b5cf7b23 100644 --- a/templates/admin/supplier_admin.html.twig +++ b/templates/admin/supplier_admin.html.twig @@ -1,7 +1,9 @@ {% extends "admin/base_company_admin.html.twig" %} {% block card_title %} - {% trans %}supplier.caption{% endtrans %} + {% set dataSourceName = get_data_source_name('supplier', 'supplier.caption') %} + {% set translatedSource = 'supplier.caption'|trans %} + {% if dataSourceName != translatedSource %}{{ 'datasource.synonym'|trans({'%name%': translatedSource, '%synonym%': dataSourceName}) }}{% else %}{{ translatedSource }}{% endif %} {% endblock %} {% block additional_panes %} diff --git a/templates/components/tree_macros.html.twig b/templates/components/tree_macros.html.twig index 2e55147a..210a0063 100644 --- a/templates/components/tree_macros.html.twig +++ b/templates/components/tree_macros.html.twig @@ -1,14 +1,16 @@ {% macro sidebar_dropdown() %} + {% set currentLocale = app.request.locale %} + {# Format is [mode, route, label, show_condition] #} {% set data_sources = [ - ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read')], - ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read')], - ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read')], - ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read')], - ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read')], - ['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read')], - ['assembly', path('tree_assembly_root'), 'assembly.labelp', is_granted('@assemblies.read')], - ['tools', path('tree_tools'), 'tools.label', true], + ['categories', path('tree_category_root'), 'category.labelp', is_granted('@categories.read') and is_granted('@parts.read'), 'category'], + ['locations', path('tree_location_root'), 'storelocation.labelp', is_granted('@storelocations.read') and is_granted('@parts.read'), 'storagelocation'], + ['footprints', path('tree_footprint_root'), 'footprint.labelp', is_granted('@footprints.read') and is_granted('@parts.read'), 'footprint'], + ['manufacturers', path('tree_manufacturer_root'), 'manufacturer.labelp', is_granted('@manufacturers.read') and is_granted('@parts.read'), 'manufacturer'], + ['suppliers', path('tree_supplier_root'), 'supplier.labelp', is_granted('@suppliers.read') and is_granted('@parts.read'), 'supplier'], + ['projects', path('tree_device_root'), 'project.labelp', is_granted('@projects.read'), 'project'], + ['assembly', path('tree_assembly_root'), 'assembly.labelp', is_granted('@assemblies.read'), 'assembly'], + ['tools', path('tree_tools'), 'tools.label', true, 'tool'], ] %} @@ -19,9 +21,9 @@ {% for source in data_sources %} {% if source[3] %} {# show_condition #} -
  • + >{{ get_data_source_name(source[4], source[2]) }} {% endif %} {% endfor %} {% endmacro %} @@ -62,4 +64,4 @@
    -{% endmacro %} \ No newline at end of file +{% endmacro %} diff --git a/templates/form/permission_layout.html.twig b/templates/form/permission_layout.html.twig index 166147b4..896a2def 100644 --- a/templates/form/permission_layout.html.twig +++ b/templates/form/permission_layout.html.twig @@ -6,12 +6,31 @@
    {% else %} - {{ form.vars.label | trans }} + def{{ form.vars.label | trans }} {% endif %} diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index 34e21fa3..baecebb9 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -14745,5 +14745,11 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz Neplatný regulární výraz (regex) + + + datasource.synonym + %name% (Váš synonymum: %synonym%) + + diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf index 5901e9f5..07f6c1b3 100644 --- a/translations/messages.da.xlf +++ b/translations/messages.da.xlf @@ -13462,5 +13462,11 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver Ugyldigt regulært udtryk (regex) + + + datasource.synonym + %name% (Dit synonym: %synonym%) + + diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index 10ca553d..a74b4fc0 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -15455,5 +15455,11 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Ungültiger regulärer Ausdruck (regex) + + + datasource.synonym + %name% (Ihr Synonym: %synonym%) + + diff --git a/translations/messages.el.xlf b/translations/messages.el.xlf index fd511453..07143e5b 100644 --- a/translations/messages.el.xlf +++ b/translations/messages.el.xlf @@ -2470,5 +2470,11 @@ Μη έγκυρη κανονική έκφραση (regex) + + + datasource.synonym + %name% (Το συνώνυμό σας: %synonym%) + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 117c263a..9559f9bf 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -15456,5 +15456,11 @@ Please note, that you can not impersonate a disabled user. If you try you will g Invalid regular expression (regex) + + + datasource.synonym + %name% (Your synonym: %synonym%) + + diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf index 5854628c..d635333f 100644 --- a/translations/messages.es.xlf +++ b/translations/messages.es.xlf @@ -13634,5 +13634,11 @@ Por favor ten en cuenta que no puedes personificar a un usuario deshabilitado. S Expresión regular no válida (regex) + + + datasource.synonym + %name% (Tu sinónimo: %synonym%) + + diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 2b4341ca..34ebcd1f 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -10044,5 +10044,11 @@ exemple de ville Expression régulière invalide (regex) + + + datasource.synonym + %name% (Votre synonyme : %synonym%) + + diff --git a/translations/messages.it.xlf b/translations/messages.it.xlf index c727eef8..b018468b 100644 --- a/translations/messages.it.xlf +++ b/translations/messages.it.xlf @@ -13636,5 +13636,11 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a Espressione regolare non valida (regex) + + + datasource.synonym + %name% (Il tuo sinonimo: %synonym%) + + diff --git a/translations/messages.ja.xlf b/translations/messages.ja.xlf index 157b1cf2..4d1c82ed 100644 --- a/translations/messages.ja.xlf +++ b/translations/messages.ja.xlf @@ -9757,5 +9757,11 @@ Exampletown 無効な正規表現(regex) + + + datasource.synonym + %name% (あなたの同義語: %synonym%) + + diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index 44a48dcb..82219181 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -1695,5 +1695,11 @@ Ongeldige reguliere expressie (regex) + + + datasource.synonym + %name% (Uw synoniem: %synonym%) + + diff --git a/translations/messages.pl.xlf b/translations/messages.pl.xlf index 171c585d..6a7e5d59 100644 --- a/translations/messages.pl.xlf +++ b/translations/messages.pl.xlf @@ -13489,5 +13489,11 @@ Należy pamiętać, że nie możesz udawać nieaktywnych użytkowników. Jeśli Nieprawidłowe wyrażenie regularne (regex) + + + datasource.synonym + %name% (Twój synonim: %synonym%) + + diff --git a/translations/messages.ru.xlf b/translations/messages.ru.xlf index 7bd0f3bc..2add6347 100644 --- a/translations/messages.ru.xlf +++ b/translations/messages.ru.xlf @@ -13589,5 +13589,11 @@ Неверное регулярное выражение (regex) + + + datasource.synonym + %name% (Ваш синоним: %synonym%) + + diff --git a/translations/messages.zh.xlf b/translations/messages.zh.xlf index 9e8d8aa1..cd6b34b2 100644 --- a/translations/messages.zh.xlf +++ b/translations/messages.zh.xlf @@ -13474,5 +13474,11 @@ Element 3 无效的正则表达式(regex) + + + datasource.synonym + %name% (您的同义词: %synonym%) + +