Skip to content

Commit e24b59c

Browse files
committed
Merge remote-tracking branch 'origin/2025.4' into 2026.x
2 parents 30cb57e + d49f96b commit e24b59c

10 files changed

Lines changed: 162 additions & 13 deletions

File tree

config/pimcore/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pimcore:
1515
- studio
1616

1717
pimcore_studio_backend:
18+
translations:
19+
path: "@PimcoreStudioBackendBundle/translations"
1820
open_api_scan_paths:
1921
- "%kernel.project_dir%/vendor/pimcore/studio-backend-bundle/src"
2022
asset_default_formats:

config/translation.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ services:
2828
Pimcore\Bundle\StudioBackendBundle\Translation\Service\ImportServiceInterface:
2929
class: Pimcore\Bundle\StudioBackendBundle\Translation\Service\ImportService
3030

31+
Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageServiceInterface:
32+
class: Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageService
33+
3134
Pimcore\Bundle\StudioBackendBundle\Translation\Service\TranslatorServiceInterface:
3235
class: Pimcore\Bundle\StudioBackendBundle\Translation\Service\TranslatorService
3336

src/DependencyInjection/Configuration.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public function getConfigTreeBuilder(): TreeBuilder
8787
$this->addGdprDataExtractorNode($rootNode);
8888
$this->addAdminSettingsNode($rootNode);
8989
$this->addMcpNode($rootNode);
90+
$this->addTranslation($rootNode);
9091
$rootNode->append($this->addTwigSandboxNode());
9192

9293
ConfigurationHelper::addConfigLocationWithWriteTargetNodes(
@@ -803,4 +804,17 @@ private function addAdminSettingsNode(ArrayNodeDefinition $node): void
803804
->end()
804805
->end();
805806
}
807+
808+
private function addTranslation(ArrayNodeDefinition $node): void
809+
{
810+
$node
811+
->children()
812+
->arrayNode('translations')
813+
->addDefaultsIfNotSet()
814+
->children()
815+
->scalarNode('path')->defaultNull()->end()
816+
->end()
817+
->end()
818+
->end();
819+
}
806820
}

src/DependencyInjection/PimcoreStudioBackendExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Pimcore\Bundle\StudioBackendBundle\Perspective\Service\WidgetValidationServiceInterface;
3939
use Pimcore\Bundle\StudioBackendBundle\Security\Authenticator\Mcp\PatAuthenticator;
4040
use Pimcore\Bundle\StudioBackendBundle\Setting\Admin\Repository\SettingRepositoryInterface;
41+
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageServiceInterface;
4142
use Pimcore\Bundle\StudioBackendBundle\Twig\Initializers\SandboxExtensionInitializerInterface;
4243
use Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingServiceInterface;
4344
use Pimcore\Bundle\StudioBackendBundle\User\Service\MailServiceInterface;
@@ -206,6 +207,10 @@ public function load(array $configs, ContainerBuilder $container): void
206207

207208
$definition = $container->getDefinition(PatAuthenticator::class);
208209
$definition->setArgument('$tokenMap', $mcpTokenMap);
210+
211+
$definition = $container->getDefinition(AdminLanguageServiceInterface::class);
212+
$definition->setArgument('$translationsPath', $config['translations']['path']);
213+
$definition->setArgument('$defaultTranslationsPath', '%translator.default_path%');
209214
}
210215

211216
/**

src/Security/Service/LanguageService.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
namespace Pimcore\Bundle\StudioBackendBundle\Security\Service;
1515

1616
use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface;
17-
use Pimcore\Bundle\StaticResolverBundle\Lib\Tools\AdminResolverInterface;
1817
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\ForbiddenException;
1918
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidArgumentException;
19+
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageServiceInterface;
2020
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\TranslatorServiceInterface;
2121
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\ElementPermissions;
2222
use Pimcore\Model\DataObject;
@@ -31,7 +31,7 @@
3131
final readonly class LanguageService implements LanguageServiceInterface
3232
{
3333
public function __construct(
34-
private AdminResolverInterface $adminResolver,
34+
private AdminLanguageServiceInterface $adminLanguageService,
3535
private SecurityServiceInterface $securityService,
3636
private ToolResolverInterface $toolResolver,
3737
) {
@@ -77,7 +77,7 @@ public function getTranslationAllowedLanguages(UserInterface $user, string $doma
7777
{
7878
$allowedLanguages = $user->getAllowedLanguagesForViewingWebsiteTranslations();
7979
if (in_array($domain, [TranslatorServiceInterface::DOMAIN, 'admin'], true)) {
80-
$allowedLanguages = $this->adminResolver->getLanguages();
80+
$allowedLanguages = $this->adminLanguageService->getAvailableAdminLanguages();
8181
}
8282

8383
return $allowedLanguages;

src/Setting/Provider/SystemSettingsProvider.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515

1616
use Pimcore\Bundle\StaticResolverBundle\Lib\ConfigResolver;
1717
use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface;
18-
use Pimcore\Bundle\StaticResolverBundle\Lib\Tools\AdminResolverInterface;
1918
use Pimcore\Bundle\StaticResolverBundle\Lib\VersionResolverInterface;
2019
use Pimcore\Bundle\StaticResolverBundle\Models\Element\ServiceResolverInterface;
2120
use Pimcore\Bundle\StudioBackendBundle\Element\Service\ElementDataServiceInterface;
21+
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageServiceInterface;
2222
use Pimcore\Bundle\StudioBackendBundle\Util\Trait\ElementProviderTrait;
2323
use Pimcore\Model\Element\ElementInterface;
2424
use Pimcore\SystemSettingsConfig;
@@ -37,12 +37,12 @@
3737

3838
public function __construct(
3939
SystemSettingsConfig $systemSettingsConfig,
40-
private AdminResolverInterface $adminResolver,
4140
private ToolResolverInterface $toolResolver,
4241
private VersionResolverInterface $versionResolver,
4342
private ConfigResolver $configResolver,
4443
private ServiceResolverInterface $serviceResolver,
45-
private ElementDataServiceInterface $elementDataService
44+
private ElementDataServiceInterface $elementDataService,
45+
private AdminLanguageServiceInterface $adminLanguageService,
4646
) {
4747
$this->systemSettings = $systemSettingsConfig->getSystemSettingsConfig();
4848
}
@@ -62,7 +62,7 @@ public function getSettings(): array
6262
'errorPages' => $this->systemSettings['error_pages'] ?? [],
6363
'redirectToMainDomain' => $this->systemSettings['redirect_to_maindomain'] ?? false,
6464
'email' => $this->systemSettings['email'] ?? [],
65-
'availableAdminLanguages' => $this->adminResolver->getLanguages(),
65+
'availableAdminLanguages' => $this->adminLanguageService->getAvailableAdminLanguages(),
6666
'validLocales' => $this->toolResolver->getSupportedJSLocales(),
6767
'debug_admin_translations' => (bool)$this->systemSettings['general']['debug_admin_translations'],
6868
'main_domain' => $this->systemSettings['general']['domain'],
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Translation\Service;
15+
16+
use Pimcore\Localization\LocaleServiceInterface;
17+
use Symfony\Component\HttpKernel\KernelInterface;
18+
19+
/**
20+
* @internal
21+
*/
22+
final readonly class AdminLanguageService implements AdminLanguageServiceInterface
23+
{
24+
public function __construct(
25+
private string $translationsPath,
26+
private string $defaultTranslationsPath,
27+
private KernelInterface $kernel,
28+
private LocaleServiceInterface $localeService,
29+
) {
30+
}
31+
32+
public function getAvailableAdminLanguages(): array
33+
{
34+
$translatedLanguages = [];
35+
36+
foreach ($this->getLanguageDirectories() as $directory) {
37+
$translatedLanguages = array_merge($translatedLanguages, $this->scanDirectoryForLanguages($directory));
38+
}
39+
40+
return array_unique($translatedLanguages);
41+
}
42+
43+
private function getLanguageDirectories(): array
44+
{
45+
$directories = [];
46+
47+
$languageDir = $this->kernel->locateResource($this->translationsPath);
48+
if (is_dir($languageDir)) {
49+
$directories[] = $languageDir;
50+
}
51+
52+
if (is_dir($this->defaultTranslationsPath)) {
53+
$directories[] = $this->defaultTranslationsPath;
54+
}
55+
56+
return $directories;
57+
}
58+
59+
private function scanDirectoryForLanguages(string $directory): array
60+
{
61+
$languages = [];
62+
$files = scandir($directory);
63+
64+
if ($files === false) {
65+
return [];
66+
}
67+
68+
foreach ($files as $file) {
69+
$languageCode = $this->extractLanguageCode($directory, $file);
70+
if ($languageCode !== null) {
71+
$languages[] = $languageCode;
72+
}
73+
}
74+
75+
return $languages;
76+
}
77+
78+
private function extractLanguageCode(string $directory, string $file): ?string
79+
{
80+
if (!is_file($directory . '/' . $file)) {
81+
return null;
82+
}
83+
84+
$parts = explode('.', $file);
85+
if (count($parts) < 2) {
86+
return null;
87+
}
88+
89+
$languageCode = $parts[0];
90+
if ($parts[0] === 'studio' && isset($parts[1])) {
91+
$languageCode = $parts[1];
92+
}
93+
94+
$extension = end($parts);
95+
if (($extension === 'json' || $parts[0] === 'studio') && $this->localeService->isLocale($languageCode)) {
96+
return $languageCode;
97+
}
98+
99+
return null;
100+
}
101+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* This source file is available under the terms of the
6+
* Pimcore Open Core License (POCL)
7+
* Full copyright and license information is available in
8+
* LICENSE.md which is distributed with this source code.
9+
*
10+
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
11+
* @license Pimcore Open Core License (POCL)
12+
*/
13+
14+
namespace Pimcore\Bundle\StudioBackendBundle\Translation\Service;
15+
16+
/**
17+
* @internal
18+
*/
19+
interface AdminLanguageServiceInterface
20+
{
21+
/**
22+
* @return string[]
23+
*/
24+
public function getAvailableAdminLanguages(): array;
25+
}

src/Translation/Service/TranslatorService.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Locale;
1919
use Pimcore\Bundle\StaticResolverBundle\Lib\CacheResolverInterface;
2020
use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface;
21-
use Pimcore\Bundle\StaticResolverBundle\Lib\Tools\AdminResolverInterface;
2221
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\InvalidLocaleException;
2322
use Pimcore\Bundle\StudioBackendBundle\Exception\Api\NotFoundException as StudioNotFoundException;
2423
use Pimcore\Bundle\StudioBackendBundle\Filter\FilterType;
@@ -57,7 +56,7 @@ public function __construct(
5756
private TranslatorInterface $translator,
5857
private TranslationRepositoryInterface $translationRepository,
5958
private SecurityServiceInterface $securityService,
60-
private AdminResolverInterface $adminResolver,
59+
private AdminLanguageServiceInterface $adminLanguageService,
6160
private ListingFilterInterface $listingFilter,
6261
private FilterMapperServiceInterface $filterMapper,
6362
private TranslationsHydratorInterface $translationsHydrator,
@@ -211,7 +210,7 @@ public function listTranslations(string $domain, CollectionFilterParameter $para
211210

212211
public function getTranslationList(string $domain, CollectionFilterParameter $parameter): Listing
213212
{
214-
$validLanguages = $this->adminResolver->getLanguages();
213+
$validLanguages = $this->adminLanguageService->getAvailableAdminLanguages();
215214

216215
$list = $this->translationRepository->getTranslationList($domain);
217216
$filters = $parameter->getFilters();

tests/Unit/Service/Translator/TranslatorServiceTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
use Exception;
1818
use Pimcore\Bundle\StaticResolverBundle\Lib\CacheResolverInterface;
1919
use Pimcore\Bundle\StaticResolverBundle\Lib\ToolResolverInterface;
20-
use Pimcore\Bundle\StaticResolverBundle\Lib\Tools\AdminResolverInterface;
2120
use Pimcore\Bundle\StudioBackendBundle\Listing\Service\FilterMapperServiceInterface;
2221
use Pimcore\Bundle\StudioBackendBundle\Listing\Service\ListingFilterInterface;
2322
use Pimcore\Bundle\StudioBackendBundle\Security\Service\SecurityServiceInterface;
2423
use Pimcore\Bundle\StudioBackendBundle\Translation\Hydrator\TranslationsHydratorInterface;
2524
use Pimcore\Bundle\StudioBackendBundle\Translation\Repository\TranslationRepositoryInterface;
25+
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\AdminLanguageServiceInterface;
2626
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\TranslatorService;
2727
use Pimcore\Bundle\StudioBackendBundle\Translation\Service\TranslatorServiceInterface;
2828
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\PublicTranslations;
@@ -84,7 +84,7 @@ private function mockTranslatorService(bool $loggedIn = true): TranslatorService
8484
$securityService = $this->makeEmpty(SecurityServiceInterface::class, [
8585
'isLoggedIn' => $loggedIn,
8686
]);
87-
$adminResolver = $this->makeEmpty(AdminResolverInterface::class);
87+
$adminLanguageService = $this->makeEmpty(AdminLanguageServiceInterface::class);
8888
$listingFilter = $this->makeEmpty(ListingFilterInterface::class);
8989
$filterMapper = $this->makeEmpty(FilterMapperServiceInterface::class);
9090
$translationsHydrator = $this->makeEmpty(TranslationsHydratorInterface::class);
@@ -96,7 +96,7 @@ private function mockTranslatorService(bool $loggedIn = true): TranslatorService
9696
$translator,
9797
$repository,
9898
$securityService,
99-
$adminResolver,
99+
$adminLanguageService,
100100
$listingFilter,
101101
$filterMapper,
102102
$translationsHydrator,

0 commit comments

Comments
 (0)