Spryker Core Back Office feature integration
Edit on GitHubThis feature integration guide expects the basic feature to be in place. The current feature integration guide adds the following functionalities:
- Translation
- Security
- OAuth 2.0/Open ID Connect Support for Zed login
Prerequisites
Ensure that the related features are installed:
NAME | VERSION | INSTALLATION GUIDE |
---|---|---|
Spryker Core | 202311.0 | Install the Spryker Core feature |
1) Install the required modules
- Install the required modules using Composer:
composer require spryker-feature/spryker-core-back-office:"202311.0" --update-with-dependencies
Make sure the following modules have been installed:
MODULE | EXPECTED DIRECTORY |
---|---|
MessengerExtension | vendor/spryker/messenger-extension |
SecurityGui | vendor/spryker/security-gui |
SecurityOauthUser | vendor/sprkyer/security-oauth-user |
Translator | vendor/spryker/translator |
UserLocale | vendor/spryker/user-locale |
UserLocaleGui | vendor/spryker/user-locale-gui |
UserPasswordReset | vendor/spryker/user-password-reset |
UserPasswordResetExtension | vendor/spryker/user-password-reset-extension |
UserPasswordResetMail | vendor/spryker/user-password-reset-mail |
SecurityBlockerBackoffice | vendor/spryker/security-blocker-backoffice |
SecurityBlockerBackofficeGui | vendor/spryker/security-blocker-backoffice-gui |
Ensure that the following modules have been removed:
MODULE | EXPECTED DIRECTORY |
---|---|
Auth | vendor/spryker/auth |
AuthMailConnector | vendor/spryker/auth-mail-connector |
AuthMailConnectorExtension | vendor/spryker/auth-mail-connector-extension |
- If these modules have not been removed, remove them:
composer remove spryker/auth spryker/auth-mail-connector spryker/auth-mail-connector-extension
2) Set up transfer objects
Generate transfers:
console transfer:generate
Ensure the following transfers have been created:
TRANSFER | TYPE | EVENT | PATH |
---|---|---|---|
UserTransfer.fkLocale | attribute | created | src/Generated/Shared/Transfer/UserTransfer |
UserTransfer.localName | attribute | created | src/Generated/Shared/Transfer/UserTransfer |
UserTransfer.username | attribute | created | src/Generated/Shared/Transfer/UserTransfer |
UserTransfer.password | attribute | created | src/Generated/Shared/Transfer/UserTransfer |
UserTransfer.lastLogin | attribute | created | src/Generated/Shared/Transfer/UserTransfer |
MessageTransfer | class | created | src/Generated/Shared/Transfer/MessageTransfer |
TranslationTransfer | class | created | src/Generated/Shared/Transfer/TranslationTransfer |
KeyTranslationTransfer | class | created | src/Generated/Shared/Transfer/KeyTranslationTransfer |
OauthAuthenticationLinkTransfer | class | created | src/Generated/Shared/Transfer/OauthAuthenticationLinkTransfer |
ResourceOwnerTransfer | class | created | src/Generated/Shared/Transfer/ResourceOwnerTransfer |
ResourceOwnerRequestTransfer | class | created | src/Generated/Shared/Transfer/ResourceOwnerRequestTransfer |
ResourceOwnerResponseTransfer | class | created | src/Generated/Shared/Transfer/ResourceOwnerResponseTransfer |
SecurityBlockerConfigurationSettingsTransfer | class | created | src/Generated/Shared/Transfer/SecurityBlockerConfigurationSettingsTransfer |
SecurityCheckAuthResponseTransfer | class | created | src/Generated/Shared/Transfer/SecurityCheckAuthResponseTransfer |
SecurityCheckAuthContextTransfer | class | created | src/Generated/Shared/Transfer/SecurityCheckAuthContextTransfer |
LocaleTransfer | class | created | src/Generated/Shared/Transfer/LocaleTransfer |
3) Set up the configuration
Add the following configuration to your project:
CONFIGURATION | SPECIFICATION | NAMESPACE |
---|---|---|
TranslatorConstants::TRANSLATION_ZED_FALLBACK_LOCALES | Fallback locales that are used if there is no translation for a selected locale. | Spryker\Shared\Translator |
TranslatorConstants::TRANSLATION_ZED_CACHE_DIRECTORY | An absolute path to a translation cache directory. For example, /var/www/data/DE/cache/Zed/translation . |
Spryker\Shared\Translator |
TranslatorConstants::TRANSLATION_ZED_FILE_PATH_PATTERNS | Paths to project level translations. You can use a global pattern that specifies sets of filenames with wildcard characters. | Spryker\Shared\Translator |
AclConstants::ACL_DEFAULT_RULES | Default rules for ACL functionality, where you can open access to some modules or controller out of the box. | Spryker\Shared\Acl |
SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_TTL | Specifies the TTL configuration, the period when the number of unsuccessful tries is counted for a Back Office user. | Spryker\Shared\SecurityBlockerBackoffice |
SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCK_FOR_SECONDS | Specifies the TTL configuration, the period for which the Back Office user is blocked if the number of attempts is exceeded for the Back Office. | Spryker\Shared\SecurityBlockerBackoffice |
SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_NUMBER_OF_ATTEMPTS | Specifies number of failed login attempts a Back Office user can make during the SECURITY_BLOCKER_BACKOFFICE:BLOCKING_TTL time before it is blocked. |
Spryker\Shared\SecurityBlockerBackoffice |
config/Shared/config_default.php
use Spryker\Shared\Translator\TranslatorConstants;
// ----------- Translator
$config[TranslatorConstants::TRANSLATION_ZED_FALLBACK_LOCALES] = [
'de_DE' => ['en_US'],
];
$config[TranslatorConstants::TRANSLATION_ZED_CACHE_DIRECTORY] = sprintf(
'%s/data/%s/cache/Zed/translation',
APPLICATION_ROOT_DIR,
$CURRENT_STORE
);
$config[TranslatorConstants::TRANSLATION_ZED_FILE_PATH_PATTERNS] = [
APPLICATION_ROOT_DIR . '/data/translation/Zed/*/[a-z][a-z]_[A-Z][A-Z].csv',
];
// >> BACKOFFICE
// ACL: Allow or disallow URLs for Zed GUI for ALL users
$config[AclConstants::ACL_DEFAULT_RULES] = [
[
'bundle' => 'security-gui',
'controller' => '*',
'action' => '*',
'type' => 'allow',
],
[
'bundle' => 'acl',
'controller' => 'index',
'action' => 'denied',
'type' => 'allow',
],
];
// Security Blocker Back Office user
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_TTL] = 900;
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCK_FOR_SECONDS] = 360;
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_NUMBER_OF_ATTEMPTS] = 9;
Set up an authentication strategy
Spryker offers two authentication strategies out of the box:
\Spryker\Zed\SecurityOauthUser\SecurityOauthUserConfig::AUTHENTICATION_STRATEGY_CREATE_USER_ON_FIRST_LOGIN
: If a user doesn’t exist, it is created automatically based on the data from an external service.\Spryker\Zed\SecurityOauthUser\SecurityOauthUserConfig::AUTHENTICATION_STRATEGY_ACCEPT_ONLY_EXISTING_USERS
: It accepts only existing users for authentication.
src/Pyz/Zed/SecurityOauthUser/SecurityOauthUserConfig.php
<?php
namespace Pyz\Zed\SecurityOauthUser;
use Spryker\Zed\SecurityOauthUser\SecurityOauthUserConfig as SprykerSecurityOauthUserConfig;
class SecurityOauthUserConfig extends SprykerSecurityOauthUserConfig
{
/**
* Specification:
* - Defines by which strategy Oauth user authentication should be.
*
* @api
*
* @return string
*/
public function getAuthenticationStrategy(): string
{
return static::AUTHENTICATION_STRATEGY_CREATE_USER_ON_FIRST_LOGIN;
}
}
After finishing the entire integration, ensure the following:
- Entries without a translation for a language with a configured fallback are translated into the fallback language.
- The translation cache is stored under the configured directory.
- Translations are found based on the configured path pattern.
Configure navigation
- Add the
StorageGui
section tonavigation.xml
:
config/Zed/navigation.xml
<?xml version="1.0"?>
<config>
<maintenance>
<label>Maintenance</label>
<title>Maintenance</title>
<icon>fa-wrench</icon>
<pages>
<storage-gui>
<label>Storage</label>
<title>Storage index</title>
<bundle>storage-gui</bundle>
<controller>maintenance</controller>
<action>index</action>
</storage-gui>
</pages>
</maintenance>
</config>
- Execute the following command:
console navigation:build-cache
In the Back Office, make sure that you can select Maintenance > Storage.
Configure the User
module to execute post save plugins:
src/Pyz/Zed/User/UserConfig.php
<?php
namespace Pyz\Zed\User;
use Spryker\Zed\User\UserConfig as SprykerUserConfig;
class UserConfig extends SprykerUserConfig
{
/**
* @var bool
*/
protected const IS_POST_SAVE_PLUGINS_ENABLED_AFTER_USER_STATUS_CHANGE = true;
}
4) Set up behavior
Set up the following behaviors.
Set up admin user login to the Back Office
- Activate the following security plugins:
PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
---|---|---|---|
SecurityApplicationPlugin | Extends the Zed global container with the services required for the Security functionality. | If there is WebProfilerApplicationPlugin in ApplicationDependencyProvider , put SecurityApplicationPlugin before it. |
Spryker\Zed\Security\Communication\Plugin\Application |
UserSessionHandlerSecurityPlugin | Sets an authenticated user to the session. | None | Spryker\Zed\User\Communication\Plugin\Securiy |
UserSecurityPlugin | Sets security firewalls, such as rules and handlers, for the Back Office users. | None | Spryker\Zed\SecurityGui\Communication\Plugin\Security |
UserPasswordResetMailTypePlugin | Adds a new email type, which is used by MailUserPasswordResetRequestHandlerPlugin . |
None | Spryker\Zed\UserPasswordResetMail\Communication\Plugin\Mail |
MailUserPasswordResetRequestHandlerPlugin | Sends a password reset email on a user request. | Mail module must be configured. UserPasswordResetMailTypePlugin is enabled. |
Spryker\Zed\UserPasswordResetMail\Communication\Plugin\UserPasswordReset |
OauthUserSecurityPlugin | Sets security firewalls, such as rules and handlers, for Oauth users. | None | \Spryker\Zed\SecurityOauthUser\Communication\Plugin\Security |
BackofficeUserSecurityBlockerConfigurationSettingsExpanderPlugin | Expands security blocker configuration settings with Back Office user security configuration. | None | \Spryker\Client\SecurityBlockerBackoffice\Plugin\SecurityBlocker |
SecurityBlockerBackofficeUserEventDispatcherPlugin | Adds a listener to log the failed Backoffice login attempts. Denies user access in case of exceeding the limit. | None | \Spryker\Zed\SecurityBlockerBackofficeGui\Communication\Plugin\EventDispatcher |
src/Pyz/Zed/Application/ApplicationDependencyProvider.php
<?php
namespace Pyz\Zed\Application;
use Spryker\Zed\Application\ApplicationDependencyProvider as SprykerApplicationDependencyProvider;
use Spryker\Zed\Security\Communication\Plugin\Application\SecurityApplicationPlugin;
class ApplicationDependencyProvider extends SprykerApplicationDependencyProvider
{
/**
* @return array<\Spryker\Shared\ApplicationExtension\Dependency\Plugin\ApplicationPluginInterface>
*/
protected function getApplicationPlugins(): array
{
return [
new SecurityApplicationPlugin(),
// web profiler plugin should be after the security plugin.
new WebProfilerApplicationPlugin(),
];
}
}
src/Pyz/Zed/Security/SecurityDependencyProvider.php
<?php
namespace Pyz\Zed\Security;
use Spryker\Zed\Security\SecurityDependencyProvider as SprykerSecurityDependencyProvider;
use Spryker\Zed\SecurityOauthUser\Communication\Plugin\Security\OauthUserSecurityPlugin;
class SecurityDependencyProvider extends SprykerSecurityDependencyProvider
{
/**
* @return array<\Spryker\Shared\SecurityExtension\Dependency\Plugin\SecurityPluginInterface>
*/
protected function getSecurityPlugins(): array
{
return [
new UserSessionHandlerSecurityPlugin(),
new UserSecurityPlugin(),
new OauthUserSecurityPlugin(),
];
}
}
src/Pyz/Client/SecurityBlocker/SecurityBlockerDependencyProvider.php
<?php
namespace Pyz\Client\SecurityBlocker;
use Spryker\Client\SecurityBlocker\SecurityBlockerDependencyProvider as SprykerSecurityBlockerDependencyProvider;
use Spryker\Client\SecurityBlockerBackoffice\Plugin\SecurityBlocker\BackofficeUserSecurityBlockerConfigurationSettingsExpanderPlugin;
class SecurityBlockerDependencyProvider extends SprykerSecurityBlockerDependencyProvider
{
/**
* @return list<\Spryker\Client\SecurityBlockerExtension\Dependency\Plugin\SecurityBlockerConfigurationSettingsExpanderPluginInterface>
*/
protected function getSecurityBlockerConfigurationSettingsExpanderPlugins(): array
{
return [
new BackofficeUserSecurityBlockerConfigurationSettingsExpanderPlugin(),
];
}
}
src/Pyz/Zed/EventDispatcher/EventDispatcherDependencyProvider.php
<?php
namespace Pyz\Zed\EventDispatcher;
use Spryker\Zed\EventDispatcher\EventDispatcherDependencyProvider as SprykerEventDispatcherDependencyProvider;
use Spryker\Zed\SecurityBlockerBackofficeGui\Communication\Plugin\EventDispatcher\SecurityBlockerBackofficeUserEventDispatcherPlugin;
class EventDispatcherDependencyProvider extends SprykerEventDispatcherDependencyProvider
{
/**
* @return array<\Spryker\Shared\EventDispatcherExtension\Dependency\Plugin\EventDispatcherPluginInterface>
*/
protected function getEventDispatcherPlugins(): array
{
return [
new SecurityBlockerBackofficeUserEventDispatcherPlugin(),
];
}
}
Ensure that https://mysprykershop.com/security-oauth-user/login
redirects you to the login page with the Authentication failed! message displayed.
src/Pyz/Zed/UserPasswordReset/UserPasswordResetDependencyProvider.php
<?php
namespace Pyz\Zed\UserPasswordReset;
use Spryker\Zed\UserPasswordReset\UserPasswordResetDependencyProvider as SprykerUserPasswordResetDependencyProvider;
use Spryker\Zed\UserPasswordResetMail\Communication\Plugin\UserPasswordReset\MailUserPasswordResetRequestHandlerPlugin;
class UserPasswordResetDependencyProvider extends SprykerUserPasswordResetDependencyProvider
{
/**
* @return array<\Spryker\Zed\UserPasswordResetExtension\Dependency\Plugin\UserPasswordResetRequestHandlerPluginInterface>
*/
public function getUserPasswordResetRequestHandlerPlugins(): array
{
return [
new MailUserPasswordResetRequestHandlerPlugin(),
];
}
}
src/Pyz/Zed/Mail/MailDependencyProvider.php
<?php
use Spryker\Zed\Mail\MailDependencyProvider as SprykerMailDependencyProvider;
use Spryker\Zed\UserPasswordResetMail\Communication\Plugin\Mail\UserPasswordResetMailTypePlugin;
class MailDependencyProvider extends SprykerMailDependencyProvider
{
/**
* @param \Spryker\Zed\Kernel\Container $container
*
* @return \Spryker\Zed\Kernel\Container
*/
public function provideBusinessLayerDependencies(Container $container)
{
$container = parent::provideBusinessLayerDependencies($container);
$container->extend(static::MAIL_TYPE_COLLECTION, function (MailTypeCollectionAddInterface $mailCollection) {
$mailCollection
->add(new UserPasswordResetMailTypePlugin());
return $mailCollection;
});
return $container;
}
}
- Rebuild Zed router and Twig caches:
console router:cache:warm-up
console twig:cache:warmer
console twig:cache:warmer
Ensure the following:
- You can open the Back Office login page or any page which requires authentication.
- On the Back Office login page, the Forgot password? button redirects you to the password reset form.
- You receive a password reset email to the email address you submitted the password reset form with.
Set up translation across the Back Office
- Activate the following plugins for translation:
PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
---|---|---|---|
TranslatorInstallerPlugin | Regenerates new translation caches for all locales of the current store. | None | Spryker\Zed\Translator\Communication\Plugin |
TranslationPlugin | Translates flash messages provided by the Messenger module. | None | Spryker\Zed\Translator\Communication\Plugin\Messenger |
TranslatorTwigPlugin | Extends Twig with Symfony’s translation extension and Spryker’s translator logic. | None | Spryker\Zed\Translator\Communication\Plugin\Twig |
UserLocaleLocalePlugin | Provides locale of the logged-in user as current locale. | Enable \Spryker\Zed\Locale\Communication\Plugin\Application\LocaleApplicationPlugin that sets the locale of the application based on the provided locale plugin. |
Spryker\Zed\UserLocale\Communication\Plugin\Locale |
AssignUserLocalePreSavePlugin | Expands UserTransfer before saving it with a locale ID and name. |
None | Spryker\Zed\UserLocale\Communication\Plugin\User |
LocaleUserExpanderPlugin | Expands UserTransfer with a locale ID and name after reading it from the database. |
None | Spryker\Zed\UserLocale\Communication\Plugin\User |
UserLocaleFormExpanderPlugin | Expands the Edit user profile form with a locale field. | None | Spryker\Zed\UserLocaleGui\Communication\Plugin |
src/Pyz/Zed/Installer/InstallerDependencyProvider.php
<?php
namespace Pyz\Zed\Installer;
use Spryker\Zed\Installer\InstallerDependencyProvider as SprykerInstallerDependencyProvider;
use Spryker\Zed\Translator\Communication\Plugin\TranslatorInstallerPlugin;
class InstallerDependencyProvider extends SprykerInstallerDependencyProvider
{
/**
* @return array<\Spryker\Zed\Installer\Dependency\Plugin\InstallerPluginInterface>
*/
public function getInstallerPlugins()
{
return [
new TranslatorInstallerPlugin(),
];
}
}
- Execute the registered installer plugins and install infrastructural data:
console setup:init-db
Ensure that the command has done the following:
- Cleaned the previous translation cache in the translation folder, which is
data/{YOUR_STORE}/cache/Zed/translation
by default. - Generated translation cache files like
catalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php
andcatalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php.meta
in the translation folder, which isdata/{YOUR_STORE}/cache/Zed/translation
by default.
src/Pyz/Zed/Messenger/MessengerDependencyProvider.php
<?php
namespace Pyz\Zed\Messenger;
use Spryker\Zed\Messenger\MessengerDependencyProvider as SprykerMessengerDependencyProvider;
use Spryker\Zed\Translator\Communication\Plugin\Messenger\TranslationPlugin;
class MessengerDependencyProvider extends SprykerMessengerDependencyProvider
{
/**
* @return array<\Spryker\Zed\MessengerExtension\Dependency\Plugin\TranslationPluginInterface>
*/
protected function getTranslationPlugins(): array
{
return [
/**
* TranslationPlugin needs to be after other translator plugins.
*/
new TranslationPlugin(),
];
}
}
src/Pyz/Zed/Twig/TwigDependencyProvider.php
<?php
namespace Pyz\Zed\Twig;
use Spryker\Zed\Translator\Communication\Plugin\Twig\TranslatorTwigPlugin;
use Spryker\Zed\Twig\TwigDependencyProvider as SprykerTwigDependencyProvider;
class TwigDependencyProvider extends SprykerTwigDependencyProvider
{
/**
* @return array<\Spryker\Shared\TwigExtension\Dependency\Plugin\TwigPluginInterface>
*/
protected function getTwigPlugins(): array
{
return [
new TranslatorTwigPlugin(),
];
}
}
Ensure that the trans
and transChoice
Twig filters work and use translations from the configured translation files.
src/Pyz/Zed/Locale/LocaleDependencyProvider.php
<?php
namespace Pyz\Zed\Locale;
use Spryker\Shared\LocaleExtension\Dependency\Plugin\LocalePluginInterface;
use Spryker\Zed\Locale\LocaleDependencyProvider as SprykerLocaleDependencyProvider;
use Spryker\Zed\UserLocale\Communication\Plugin\Locale\UserLocaleLocalePlugin;
class LocaleDependencyProvider extends SprykerLocaleDependencyProvider
{
/**
* @return \Spryker\Shared\LocaleExtension\Dependency\Plugin\LocalePluginInterface
*/
protected function getLocalePlugin(): LocalePluginInterface
{
return new UserLocaleLocalePlugin();
}
}
Ensure that the locale of the Back Office matches the locale of a logged-in user.
src/Pyz/Zed/User/UserDependencyProvider.php
<?php
namespace Pyz\Zed\User;
use Spryker\Zed\User\UserDependencyProvider as SprykerUserDependencyProvider;
use Spryker\Zed\UserLocale\Communication\Plugin\User\AssignUserLocalePreSavePlugin;
use Spryker\Zed\UserLocale\Communication\Plugin\User\LocaleUserExpanderPlugin;
use Spryker\Zed\UserLocaleGui\Communication\Plugin\UserLocaleFormExpanderPlugin;
class UserDependencyProvider extends SprykerUserDependencyProvider
{
/**
* @return array<\Spryker\Zed\UserExtension\Dependency\Plugin\UserFormExpanderPluginInterface>
*/
protected function getUserFormExpanderPlugins(): array
{
return [
new UserLocaleFormExpanderPlugin(),
];
}
/**
* @return array<\Spryker\Zed\UserExtension\Dependency\Plugin\UserPreSavePluginInterface>
*/
protected function getUserPreSavePlugins(): array
{
return [
new AssignUserLocalePreSavePlugin(),
];
}
/**
* @return array<\Spryker\Zed\UserExtension\Dependency\Plugin\UserExpanderPluginInterface>
*/
protected function getUserExpanderPlugins(): array
{
return [
new LocaleUserExpanderPlugin(),
];
}
}
Ensure that you’ve enabled the plugins:
- In the Back Office, select Users > Users.
- Select Add New User.
- On the Create new User page, check that the Interface language* field exists.
- Add translations
Append glossary according to your configuration:
data/import/common/common/glossary.csv
security_blocker_backoffice_gui.error.account_blocked,"Too many log in attempts from your address. Please wait %minutes% minutes before trying again.",en_US
security_blocker_backoffice_gui.error.account_blocked,"Warten Sie bitte %minutes% Minuten, bevor Sie es erneut versuchen.",de_DE
Set up console commands for cache
- Set up the following console commands:
COMMAND | SPECIFICATION | PREREQUISITES | NAMESPACE |
---|---|---|---|
CleanTranslationCacheConsole | Cleans translation cache for Zed. | None | Spryker\Zed\Translator\Communication\Console |
GenerateTranslationCacheConsole | Generates translation cache for Zed. | None | Spryker\Zed\Translator\Communication\Console |
src/Pyz/Zed/Console/ConsoleDependencyProvider.php
<?php
namespace Pyz\Zed\Console;
use Spryker\Zed\Kernel\Container;
use Spryker\Zed\Console\ConsoleDependencyProvider as SprykerConsoleDependencyProvider;
use Spryker\Zed\Translator\Communication\Console\CleanTranslationCacheConsole;
use Spryker\Zed\Translator\Communication\Console\GenerateTranslationCacheConsole;
class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
/**
* @param \Spryker\Zed\Kernel\Container $container
*
* @return array<\Symfony\Component\Console\Command\Command>
*/
protected function getConsoleCommands(Container $container)
{
$commands = [
new CleanTranslationCacheConsole(),
new GenerateTranslationCacheConsole(),
];
return $commands;
}
}
- Regenerate translation cache:
console translator:clean-cache
console translator:generate-cache
Ensure that the command has done the following:
- Cleaned the previous translation cache in the translation folder, which is
data/{YOUR_STORE}/cache/Zed/translation
by default. - Generated translator cache files like
catalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php
andcatalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php.meta
in the translation folder, which isdata/{YOUR_STORE}/cache/Zed/translation
by default.
Thank you!
For submitting the form