diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml index cbc1cd7e5..a3f529e31 100644 --- a/config/packages/translation.yaml +++ b/config/packages/translation.yaml @@ -1,7 +1,7 @@ framework: default_locale: 'en' # Just enable the locales we need for performance reasons. - enabled_locale: '%partdb.locale_menu%' + enabled_locale: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] translator: default_path: '%kernel.project_dir%/translations' fallbacks: diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml index 674aa3177..789560261 100644 --- a/config/packages/twig.yaml +++ b/config/packages/twig.yaml @@ -7,7 +7,7 @@ twig: globals: allow_email_pw_reset: '%partdb.users.email_pw_reset%' - locale_menu: '%partdb.locale_menu%' + location_settings: '@App\Settings\SystemSettings\LocalizationSettings' attachment_manager: '@App\Services\Attachments\AttachmentManager' label_profile_dropdown_helper: '@App\Services\LabelSystem\LabelProfileDropdownHelper' error_page_admin_email: '%partdb.error_pages.admin_email%' @@ -20,4 +20,4 @@ twig: when@test: twig: - strict_variables: true \ No newline at end of file + strict_variables: true diff --git a/config/parameters.yaml b/config/parameters.yaml index 5b40899dc..03ccde155 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -8,7 +8,6 @@ parameters: # This is used as workaround for places where we can not access the settings directly (like the 2FA application names) partdb.title: '%env(string:settings:customization:instanceName)%' # The title shown inside of Part-DB (e.g. in the navbar and on homepage) - partdb.locale_menu: ['en', 'de', 'it', 'fr', 'ru', 'ja', 'cs', 'da', 'zh', 'pl'] # The languages that are shown in user drop down menu partdb.default_uri: '%env(string:DEFAULT_URI)%' # The default URI to use for the Part-DB instance (e.g. https://part-db.example.com/). This is used for generating links in emails diff --git a/config/services.yaml b/config/services.yaml index 17611ceab..a74bf0909 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -188,6 +188,13 @@ services: $fontDirectory: '%kernel.project_dir%/var/dompdf/fonts/' $tmpDirectory: '%kernel.project_dir%/var/dompdf/tmp/' + #################################################################################################################### + # Twig Extensions + #################################################################################################################### + + App\Twig\DataSourceNameExtension: + tags: [ 'twig.extension' ] + #################################################################################################################### # Part info provider system #################################################################################################################### diff --git a/docs/configuration.md b/docs/configuration.md index d4b217816..5ab376ec6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -262,8 +262,6 @@ command `bin/console cache:clear`. The following options are available: -* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the - user icon in the navbar). The first language in the list will be the default language. * `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU. diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index d78aff620..5d3536155 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -61,7 +61,7 @@ public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHel 'default_timezone' => $settings->system->localization->timezone, 'default_currency' => $settings->system->localization->baseCurrency, 'default_theme' => $settings->system->customization->theme, - 'enabled_locales' => $this->getParameter('partdb.locale_menu'), + 'enabled_locales' => array_column($settings->system->localization->preferredLanguages, 'value'), 'demo_mode' => $this->getParameter('partdb.demo_mode'), 'use_gravatar' => $settings->system->privacy->useGravatar, 'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'), diff --git a/src/Form/Type/DataSourceJsonType.php b/src/Form/Type/DataSourceJsonType.php new file mode 100644 index 000000000..6d11058a2 --- /dev/null +++ b/src/Form/Type/DataSourceJsonType.php @@ -0,0 +1,103 @@ +settings->dataSourceSynonyms; + } + + foreach ($dataSources as $key => $label) { + $initialData = $existingData[$key] ?? $defaultValues[$key] ?? '{}'; + + $builder->add($key, TextareaType::class, [ + 'label' => $label, + 'required' => false, + 'data' => $initialData, + 'attr' => [ + 'rows' => 3, + 'style' => 'font-family: monospace;', + 'placeholder' => sprintf('%s translations in JSON format', ucfirst($key)), + ], + 'constraints' => [ + new Assert\Callback(function ($value, $context) { + if ($value && !static::isValidJson($value)) { + $context->buildViolation('The field must contain valid JSON.')->addViolation(); + } + }), + ], + ]); + } + + $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use ($defaultValues) { + $data = $event->getData(); + + if (!$data) { + $event->setData($defaultValues); + return; + } + + foreach ($defaultValues as $key => $defaultValue) { + if (empty($data[$key])) { + $data[$key] = $defaultValue; + } else { + $decodedValue = json_decode($data[$key], true); + if (json_last_error() === JSON_ERROR_NONE) { + $data[$key] = json_encode($decodedValue, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + } + } + } + + $event->setData($data); + }); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_sources' => [], + 'default_values' => [], + ]); + + $resolver->setAllowedTypes('data_sources', 'array'); + $resolver->setAllowedTypes('default_values', 'array'); + } + + /** + * Validates if a string is a valid JSON format. + * + * @param string $json + * @return bool + */ + public static function isValidJson(string $json): bool + { + json_decode($json); + return json_last_error() === JSON_ERROR_NONE; + } +} diff --git a/src/Form/Type/LocaleSelectType.php b/src/Form/Type/LocaleSelectType.php index d47fb57fd..b87932d12 100644 --- a/src/Form/Type/LocaleSelectType.php +++ b/src/Form/Type/LocaleSelectType.php @@ -23,7 +23,7 @@ namespace App\Form\Type; -use Symfony\Component\DependencyInjection\Attribute\Autowire; +use App\Settings\SystemSettings\LocalizationSettings; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\LocaleType; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -35,7 +35,7 @@ class LocaleSelectType extends AbstractType { - public function __construct(#[Autowire(param: 'partdb.locale_menu')] private readonly array $preferred_languages) + public function __construct(private LocalizationSettings $localizationSetting) { } @@ -47,7 +47,7 @@ public function getParent(): string public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'preferred_choices' => $this->preferred_languages, + 'preferred_choices' => array_column($this->localizationSetting->preferredLanguages, 'value'), ]); } } diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index 036797f61..1f8becb7d 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -37,6 +37,7 @@ use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; use App\Services\Cache\UserCacheKeyGenerator; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Contracts\Cache\ItemInterface; @@ -49,8 +50,14 @@ */ 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 DataSourceSynonymsSettings $dataSourceSynonymsSettings, + ) { } /** @@ -138,7 +145,7 @@ protected function getToolsNode(): array $this->translator->trans('info_providers.search.title'), $this->urlGenerator->generate('info_providers_search') ))->setIcon('fa-treeview fa-fw fa-solid fa-cloud-arrow-down'); - + $nodes[] = (new TreeViewNode( $this->translator->trans('info_providers.bulk_import.manage_jobs'), $this->urlGenerator->generate('bulk_info_provider_manage') @@ -165,37 +172,37 @@ protected function getEditNodes(): array } 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 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'); } @@ -303,4 +310,24 @@ protected function getSystemNodes(): array return $nodes; } + + protected function getTranslatedDataSourceOrSynonym(string $dataSource, string $translationKey, string $locale): string + { + $currentTranslation = $this->translator->trans($translationKey); + + $synonyms = $this->dataSourceSynonymsSettings->getSynonymsAsArray(); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($synonyms[$dataSource][$locale])) { + $alternativeTranslation = $synonyms[$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 73ffa5baf..4b30cb184 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -38,6 +38,7 @@ use App\Services\Cache\ElementCacheTagGenerator; use App\Services\Cache\UserCacheKeyGenerator; use App\Services\EntityURLGenerator; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use App\Settings\BehaviorSettings\SidebarSettings; use Doctrine\ORM\EntityManagerInterface; use InvalidArgumentException; @@ -67,6 +68,7 @@ public function __construct( protected TranslatorInterface $translator, private readonly UrlGeneratorInterface $router, private readonly SidebarSettings $sidebarSettings, + protected DataSourceSynonymsSettings $dataSourceSynonymsSettings, ) { $this->rootNodeEnabled = $this->sidebarSettings->rootNodeEnabled; $this->rootNodeExpandedByDefault = $this->sidebarSettings->rootNodeExpanded; @@ -212,13 +214,15 @@ protected function entityClassToRootNodeHref(string $class): ?string 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'), + 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), default => $this->translator->trans('tree.root_node.text'), }; } @@ -274,4 +278,24 @@ public function getGenericTree(string $class, ?AbstractStructuralDBElement $pare return $repo->getGenericNodeTree($parent); //@phpstan-ignore-line }); } + + protected function getTranslatedOrSynonym(string $key, string $locale): string + { + $currentTranslation = $this->translator->trans($key . '.labelp'); + + $synonyms = $this->dataSourceSynonymsSettings->getSynonymsAsArray(); + + // Call alternatives from DataSourcesynonyms (if available) + if (!empty($synonyms[$key][$locale])) { + $alternativeTranslation = $synonyms[$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/Settings/BehaviorSettings/DataSourceSynonymsSettings.php b/src/Settings/BehaviorSettings/DataSourceSynonymsSettings.php new file mode 100644 index 000000000..74b9a2a1c --- /dev/null +++ b/src/Settings/BehaviorSettings/DataSourceSynonymsSettings.php @@ -0,0 +1,73 @@ + '{"en":"", "de":""}']), + options: ['type' => StringType::class], + formType: DataSourceJsonType::class, + formOptions: [ + 'required' => false, + 'data_sources' => [ + 'category' => new TM("settings.behavior.data_source_synonyms.category"), + 'storagelocation' => new TM("settings.behavior.data_source_synonyms.storagelocation"), + 'footprint' => new TM("settings.behavior.data_source_synonyms.footprint"), + 'manufacturer' => new TM("settings.behavior.data_source_synonyms.manufacturer"), + 'supplier' => new TM("settings.behavior.data_source_synonyms.supplier"), + 'project' => new TM("settings.behavior.data_source_synonyms.project"), + ], + 'default_values' => [ + 'category' => '{"en":"Categories", "de":"Kategorien"}', + 'storagelocation' => '{"en":"Storage locations", "de":"Lagerorte"}', + 'footprint' => '{"en":"Footprints", "de":"Footprints"}', + 'manufacturer' => '{"en":"Manufacturers", "de":"Hersteller"}', + 'supplier' => '{"en":"Suppliers", "de":"Lieferanten"}', + 'project' => '{"en":"Projects", "de":"Projekte"}', + ], + ], + )] + #[Assert\Type('array')] + public array $dataSourceSynonyms = [ + 'category' => '{"en":"Categories", "de":"Kategorien"}', + 'storagelocation' => '{"en":"Storage locations", "de":"Lagerorte"}', + 'footprint' => '{"en":"Footprints", "de":"Footprints"}', + 'manufacturer' => '{"en":"Manufacturers", "de":"Hersteller"}', + 'supplier' => '{"en":"Suppliers", "de":"Lieferanten"}', + 'project' => '{"en":"Projects", "de":"Projekte"}', + ]; + + /** + * Get the synonyms data as a structured array. + * + * @return array> The data source synonyms parsed from JSON to array. + */ + public function getSynonymsAsArray(): array + { + $result = []; + foreach ($this->dataSourceSynonyms as $key => $jsonString) { + $result[$key] = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR) ?? []; + } + + return $result; + } + +} diff --git a/src/Settings/SystemSettings.php b/src/Settings/SystemSettings.php index 83d00afc0..a81f5871c 100644 --- a/src/Settings/SystemSettings.php +++ b/src/Settings/SystemSettings.php @@ -23,6 +23,7 @@ namespace App\Settings; +use App\Settings\BehaviorSettings\DataSourceSynonymsSettings; use App\Settings\SystemSettings\AttachmentsSettings; use App\Settings\SystemSettings\CustomizationSettings; use App\Settings\SystemSettings\HistorySettings; @@ -37,6 +38,9 @@ class SystemSettings #[EmbeddedSettings()] public ?LocalizationSettings $localization = null; + #[EmbeddedSettings] + public ?DataSourceSynonymsSettings $dataSourceSynonyms = null; + #[EmbeddedSettings()] public ?CustomizationSettings $customization = null; @@ -48,4 +52,4 @@ class SystemSettings #[EmbeddedSettings()] public ?HistorySettings $history = null; -} \ No newline at end of file +} diff --git a/src/Settings/SystemSettings/LocalizationSettings.php b/src/Settings/SystemSettings/LocalizationSettings.php index 434a4e69e..bc52f4880 100644 --- a/src/Settings/SystemSettings/LocalizationSettings.php +++ b/src/Settings/SystemSettings/LocalizationSettings.php @@ -26,6 +26,8 @@ use App\Form\Type\LocaleSelectType; use App\Settings\SettingsIcon; use Jbtronics\SettingsBundle\Metadata\EnvVarMode; +use Jbtronics\SettingsBundle\ParameterTypes\ArrayType; +use Jbtronics\SettingsBundle\ParameterTypes\EnumType; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; @@ -60,4 +62,19 @@ class LocalizationSettings envVar: "string:BASE_CURRENCY", envVarMode: EnvVarMode::OVERWRITE )] public string $baseCurrency = 'EUR'; -} \ No newline at end of file + + /** @var PreferredLocales[] */ + #[SettingsParameter(ArrayType::class, + label: new TM("settings.system.localization.preferred_languages"), + description: new TM("settings.system.localization.preferred_languages.help"), + options: ['type' => EnumType::class, 'options' => ['class' => PreferredLocales::class]], + formType: \Symfony\Component\Form\Extension\Core\Type\EnumType::class, + formOptions: ['class' => PreferredLocales::class, 'multiple' => true, 'ordered' => true] + )] + #[Assert\NotBlank()] + #[Assert\Unique()] + #[Assert\All([new Assert\Type(PreferredLocales::class)])] + public array $preferredLanguages = [PreferredLocales::EN, PreferredLocales::DE, + PreferredLocales::IT, PreferredLocales::FR, PreferredLocales::RU, PreferredLocales::JA, + PreferredLocales::CS, PreferredLocales::DA, PreferredLocales::ZH, PreferredLocales::PL]; +} diff --git a/src/Settings/SystemSettings/PreferredLocales.php b/src/Settings/SystemSettings/PreferredLocales.php new file mode 100644 index 000000000..1fe38a548 --- /dev/null +++ b/src/Settings/SystemSettings/PreferredLocales.php @@ -0,0 +1,37 @@ +. + */ + +declare(strict_types=1); + +namespace App\Settings\SystemSettings; + +enum PreferredLocales: string +{ + case EN = 'en'; + case DE = 'de'; + case IT = 'it'; + case FR = 'fr'; + case RU = 'ru'; + case JA = 'ja'; + case CS = 'cs'; + case DA = 'da'; + case ZH = 'zh'; + case PL = 'pl'; +} diff --git a/src/Twig/DataSourceNameExtension.php b/src/Twig/DataSourceNameExtension.php new file mode 100644 index 000000000..d0d8b4b52 --- /dev/null +++ b/src/Twig/DataSourceNameExtension.php @@ -0,0 +1,43 @@ +translator = $translator; + $this->dataSourceSynonyms = $dataSourceSynonymsSettings->getSynonymsAsArray(); + } + + 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); + } +} diff --git a/templates/_turbo_control.html.twig b/templates/_turbo_control.html.twig index 4c1780386..46637587c 100644 --- a/templates/_turbo_control.html.twig +++ b/templates/_turbo_control.html.twig @@ -22,9 +22,9 @@
- {% for locale in locale_menu %} + {% for locale in location_settings.preferredLanguages %} - {{ locale|language_name }} ({{ locale|upper }}) + app.request.query.all|merge(app.request.attributes.get('_route_params'))|merge({'_locale': locale.value})) }}"> + {{ locale.value|language_name }} ({{ locale.value|upper }}) {% endfor %} -
\ No newline at end of file + diff --git a/templates/admin/category_admin.html.twig b/templates/admin/category_admin.html.twig index 5811640b9..f1fe7663a 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 a2c3e4afd..a6acbe84e 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 5db892c04..3ce9a124c 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 1a9950691..8066d5451 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 c93339dc1..1e60eeea2 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 ce38a5ca4..b5cf7b236 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 366d42fe8..e82cd3b4b 100644 --- a/templates/components/tree_macros.html.twig +++ b/templates/components/tree_macros.html.twig @@ -1,13 +1,15 @@ {% 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')], - ['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'], + ['tools', path('tree_tools'), 'tools.label', true, 'tool'], ] %} @@ -18,9 +20,9 @@ {% for source in data_sources %} {% if source[3] %} {# show_condition #} -
  • + >{{ get_data_source_name(source[4], source[2]) }} {% endif %} {% endfor %} {% endmacro %} @@ -61,4 +63,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 166147b4c..3c7f799ed 100644 --- a/templates/form/permission_layout.html.twig +++ b/templates/form/permission_layout.html.twig @@ -6,12 +6,34 @@
    {% else %} - {{ form.vars.label | trans }} + def{{ form.vars.label | trans }} {% endif %} @@ -110,4 +132,4 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index 1f234450e..ae29c6da8 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -12669,6 +12669,72 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz <b>Upozorňujeme, že při změně této hodnoty nedochází k převodu měn. Změna výchozí měny po přidání informací o cenách tedy povede k nesprávným cenám!</b> + + + settings.system.localization.preferred_languages + Preferované jazyky + + + + + settings.system.localization.preferred_languages.help + Jazyky, které se zobrazují v uživatelské rozbalovací nabídce + + + + + settings.system.data_source_synonyms + Synonyma zdrojů dat + + + + + settings.system.data_source_synonyms.configuration + Zdroj + + + + + settings.system.data_source_synonyms.configuration.help + Definujte vlastní synonyma pro dané zdroje dat. Očekává se formát JSON s vašimi preferovanými jazykovými ISO kódy. Příklad: %format%. + + + + + settings.behavior.data_source_synonyms.category + Kategorie + + + + + settings.behavior.data_source_synonyms.storagelocation + Skladové umístění + + + + + settings.behavior.data_source_synonyms.footprint + Pouzdro + + + + + settings.behavior.data_source_synonyms.manufacturer + Výrobce + + + + + settings.behavior.data_source_synonyms.supplier + Dodavatel + + + + + settings.behavior.data_source_synonyms.project + Projekt + + settings.system.privacy @@ -13479,5 +13545,11 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz Minimální šířka náhledu (px) + + + datasource.synonym + %name% (Váš synonymum: %synonym%) + + diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf index d72589864..e33a90357 100644 --- a/translations/messages.da.xlf +++ b/translations/messages.da.xlf @@ -12196,5 +12196,11 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver Du forsøgte at fjerne/tilføje en mængde sat til nul! Der blev ikke foretaget nogen handling. + + + datasource.synonym + %name% (Dit synonym: %synonym%) + + diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index 06326a21e..44d62d4e2 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -12749,6 +12749,72 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön <b>Bitte beachten Sie, dass die Währungen bei einer Änderung dieses Wertes nicht umgerechnet werden. Wenn Sie also die Basiswährung ändern, nachdem Sie bereits Preisinformationen hinzugefügt haben, führt dies zu falschen Preisen!</b> + + + settings.system.localization.preferred_languages + Bevorzugte Sprachen + + + + + settings.system.localization.preferred_languages.help + Die Sprachen, die im Dropdown-Menü der Nutzer angezeigt werden + + + + + settings.system.data_source_synonyms + Datenquellen-Synonyme + + + + + settings.system.data_source_synonyms.configuration + Quelle + + + + + settings.system.data_source_synonyms.configuration.help + Definieren Sie Ihre eigenen Synonyme für die angegebenen Datenquellen. Erwartet wird ein JSON-Format mit Ihren bevorzugten Sprache-ISO-Codes. Beispiel: %format%. + + + + + settings.behavior.data_source_synonyms.category + Kategorie + + + + + settings.behavior.data_source_synonyms.storagelocation + Lagerort + + + + + settings.behavior.data_source_synonyms.footprint + Footprint + + + + + settings.behavior.data_source_synonyms.manufacturer + Hersteller + + + + + settings.behavior.data_source_synonyms.supplier + Lieferant + + + + + settings.behavior.data_source_synonyms.project + Projekt + + settings.system.privacy @@ -14189,5 +14255,11 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Maximale Anzahl von Zuordnungen erreicht + + + datasource.synonym + %name% (Ihr Synonym: %synonym%) + + diff --git a/translations/messages.el.xlf b/translations/messages.el.xlf index cc17d9be4..86e7737f2 100644 --- a/translations/messages.el.xlf +++ b/translations/messages.el.xlf @@ -1535,5 +1535,11 @@ Επεξεργασία + + + datasource.synonym + %name% (Το συνώνυμό σας: %synonym%) + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index a2ec2f65c..2b4f000b2 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -12750,6 +12750,72 @@ Please note, that you can not impersonate a disabled user. If you try you will g <b>Please note that the currencies are not converted, when changing this value. So changing the default currency after you already added price information, will result in wrong prices!</b> + + + settings.system.localization.preferred_languages + Preferred languages + + + + + settings.system.localization.preferred_languages.help + The languages that are shown in user drop down menu + + + + + settings.system.data_source_synonyms + Data source synonyms + + + + + settings.system.data_source_synonyms.configuration + Source + + + + + settings.system.data_source_synonyms.configuration.help + Define your own synonyms for the given data sources. Expected in JSON-format with your preferred language iso-codes. Example: %format%. + + + + + settings.behavior.data_source_synonyms.category + Category + + + + + settings.behavior.data_source_synonyms.storagelocation + Storage location + + + + + settings.behavior.data_source_synonyms.footprint + Footprint + + + + + settings.behavior.data_source_synonyms.manufacturer + Manufacturer + + + + + settings.behavior.data_source_synonyms.supplier + Supplier + + + + + settings.behavior.data_source_synonyms.project + Project + + settings.system.privacy @@ -14190,5 +14256,11 @@ Please note, that you can not impersonate a disabled user. If you try you will g Maximum number of mappings reached + + + datasource.synonym + %name% (Your synonym: %synonym%) + + diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf index fce38e52f..83c54fcbc 100644 --- a/translations/messages.es.xlf +++ b/translations/messages.es.xlf @@ -12368,5 +12368,11 @@ Por favor ten en cuenta que no puedes personificar a un usuario deshabilitado. S Este componente contiene más de un stock. Cambie la ubicación manualmente para seleccionar el stock deseado. + + + datasource.synonym + %name% (Tu sinónimo: %synonym%) + + diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index 292dbafaa..a010463f4 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -6947,7 +6947,7 @@ Si vous avez fait cela de manière incorrecte ou si un ordinateur n'est plus fia company.edit.address.placeholder - Ex. 99 exemple de rue + Ex. 99 exemple de rue exemple de ville @@ -9097,5 +9097,11 @@ exemple de ville Si vous avez des questions à propos de Part-DB , rendez vous sur <a href="%href%" class="link-external" target="_blank">Github</a> + + + datasource.synonym + %name% (Votre synonyme : %synonym%) + + diff --git a/translations/messages.it.xlf b/translations/messages.it.xlf index 828304eba..d98cd2a2a 100644 --- a/translations/messages.it.xlf +++ b/translations/messages.it.xlf @@ -12370,5 +12370,11 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a Questo componente contiene più di uno stock. Cambia manualmente la posizione per selezionare quale stock scegliere. + + + datasource.synonym + %name% (Il tuo sinonimo: %synonym%) + + diff --git a/translations/messages.ja.xlf b/translations/messages.ja.xlf index 4becc319c..e91f4f97c 100644 --- a/translations/messages.ja.xlf +++ b/translations/messages.ja.xlf @@ -8834,5 +8834,11 @@ Exampletown Part-DBについての質問は、<a href="%href%" class="link-external" target="_blank">GitHub</a> にスレッドがあります。 + + + datasource.synonym + %name% (あなたの同義語: %synonym%) + + diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index 760533d7c..eb1597ba9 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -724,5 +724,11 @@ Weet u zeker dat u wilt doorgaan? + + + datasource.synonym + %name% (Uw synoniem: %synonym%) + + diff --git a/translations/messages.pl.xlf b/translations/messages.pl.xlf index b769e2737..7d135d2ac 100644 --- a/translations/messages.pl.xlf +++ b/translations/messages.pl.xlf @@ -12223,5 +12223,11 @@ Należy pamiętać, że nie możesz udawać nieaktywnych użytkowników. Jeśli Wygenerowany kod + + + datasource.synonym + %name% (Twój synonim: %synonym%) + + diff --git a/translations/messages.ru.xlf b/translations/messages.ru.xlf index 62570acb0..4acf1ac19 100644 --- a/translations/messages.ru.xlf +++ b/translations/messages.ru.xlf @@ -731,7 +731,7 @@ user.edit.tfa.disable_tfa_message - Это выключит <b>все активные двухфакторной способы аутентификации пользователя</b>и удалит <b>резервные коды</b>! + Это выключит <b>все активные двухфакторной способы аутентификации пользователя</b>и удалит <b>резервные коды</b>! <br> Пользователь должен будет снова настроить все методы двухфакторной аутентификации и распечатать новые резервные коды! <br><br> <b>Делайте это только в том случае, если вы абсолютно уверены в личности пользователя (обращающегося за помощью), в противном случае учетная запись может быть взломана злоумышленником!</b> @@ -3740,7 +3740,7 @@ tfa_backup.reset_codes.confirm_message - Это удалит все предыдущие коды и создаст набор новых. Это не может быть отменено. + Это удалит все предыдущие коды и создаст набор новых. Это не может быть отменено. Не забудьте распечатать новы кода и хранить их в безопасном месте! @@ -12323,5 +12323,11 @@ Профиль сохранен! + + + datasource.synonym + %name% (Ваш синоним: %synonym%) + + diff --git a/translations/messages.zh.xlf b/translations/messages.zh.xlf index 668c32f28..bc76efb37 100644 --- a/translations/messages.zh.xlf +++ b/translations/messages.zh.xlf @@ -12208,5 +12208,11 @@ Element 3 成功创建 %COUNT% 个元素。 + + + datasource.synonym + %name% (您的同义词: %synonym%) + +