Install the Spryker Core Back Office feature

Edit on GitHub

This 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

  1. Install the required modules using Composer:
composer require spryker-feature/spryker-core-back-office:"202311.0" --update-with-dependencies
Verification

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
  1. 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
Verification

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;
    }
}
Verification

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

  1. Add the StorageGui section to navigation.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>
  1. Execute the following command:
console navigation:build-cache
Verification

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

  1. 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(),
        ];
    }
}
Verification

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;
    }
}
  1. Rebuild Zed router and Twig caches:
console router:cache:warm-up
console twig:cache:warmer
console twig:cache:warmer
Verification

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

  1. 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(),
        ];
    }
}
  1. Execute the registered installer plugins and install infrastructural data:
console setup:init-db
Verification

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 and catalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php.meta in the translation folder, which is data/{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(),
        ];
    }
}
Verification

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();
    }
}
Verification

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(),
        ];
    }
}
Verification

Ensure that you’ve enabled the plugins:

  1. In the Back Office, select Users > Users.
  2. Select Add New User.
  3. On the Create new User page, check that the Interface language* field exists.
  1. 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

  1. 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;
     }
}
  1. Regenerate translation cache:
console translator:clean-cache
console translator:generate-cache
Verification

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 and catalogue.{YOUR_LOCALE}.{RANDOM_STRING}.php.meta in the translation folder, which is data/{YOUR_STORE}/cache/Zed/translation by default.