Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### Shopware
/src/Resources/app/storefront/dist/
/src/Resources/app/storefront/node_modules/
src/Resources/public/administration
src/Resources/public/storefront
src/Resources/public/static/css
src/Resources/public/static/js

### PHPUnit
/.phpunit.cache
/.phpunit.result.cache
/.coverage

### Composer
/vendor/
composer.lock
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Do
- use this docker image to test ghcr.io/netlogix/docker/php-cli-dev:8.4
- Run tests with `composer run test`
- Run `composer run apply-coding-standard` after code changes and before test
- Run `composer run lint` and fix issues after changes and after tests

## Commands

### composer run with php 8.4
```bash
docker run --rm -it -v"$(pwd):/var/www" ghcr.io/netlogix/docker/php-cli-dev:8.4 composer run
```

### composer run with php 8.4
```bash
docker run --rm -it -v"$(pwd):/var/www" ghcr.io/netlogix/docker/php-cli-dev:8.4 composer run
```

102 changes: 100 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,101 @@
# Symfony Translation Bridge
# Shopware Translation Bridge

With this plugin you can use the provider which can be defined in symfony/trnaslation to manage the storefront snippets.
This plugin provides a bridge to connect Shopware with any Translation Provider that is supported by the [Symfony Translation Component](https://symfony.com/doc/current/translation.html#translation-providers). It allows you to manage your storefront snippets via a third-party translation service.

## Installation

```bash
composer require netlogix/shopware-translation-bridge
bin/console plugin:install --activate ShopwareTranslationBridge
```

## Configuration

The connection to the translation provider is configured via a DSN (Data Source Name). You need to create a configuration file, for example `config/packages/shopware_translation_bridge.yaml`, to set up the providers.

The plugin uses the DSN from the `ShopwareTranslationBridge.config.providerDsn` system config key as a default. You can also configure a specific DSN for each sales channel.

| option | type | default | info |
|---------------------------|----------------|---------|----------------------------------------------------------------------------------------------------|
| default_provider | `null\|string` | `null` | Service name from `framework.translator.providers`. If `null` there is no fallback provider. |
| respect_translation_files | `bool` | `true` | should it overlay the snippet files with the translation files `framework.translator.default_path` |
| sales_channel_providers | `array` | `[]` | SalesChannel specific providers. Like `default_provider` but individiual for every salesChannel |

### Example Configuration

Here is an example of how to configure different providers for different sales channels.

```yaml
# config/packages/shopware_translation_bridge.yaml
shopware_translation_bridge:
# Define a default provider for all sales channels
default_provider: 'providerServiceName'
respect_translation_files: true
sales_channel_providers:
# Assign a specific provider for a sales channel by its ID
2b919afec10730f413cb5682bbed09fd:
provider: 'providerServiceName'
```

## Commands

This plugin provides three commands to manage translations.

### Push Snippets

Pushes all local snippets to the configured translation provider.

```bash
bin/console sw:snippets:push [salesChannelId1] [salesChannelId2]
```

**Arguments:**

* `salesChannelId` (optional, multiple): The sales channel ID(s) to push translations for. If "default" or empty, the default provider is used.

**Options:**

* `--force` / `-f`: Overwrite existing translations on the provider.
* `--delete-missing`: Delete translations on the provider that do not exist locally.
* `--locales` / `-l` (multiple): Specify the locales to push (e.g., `en-GB`, `de-DE`). If not provided, all relevant locales are pushed.

### Pull Snippets

Pulls all snippets from the configured translation provider and saves them locally inside the translation directory defined by `framework.translator.default_path`. The default provider (if configured) is written to the `messages` translation domain, while every entry of `sales_channel_providers` is persisted to a domain that matches the configured sales channel id.

```bash
bin/console sw:snippets:pull [salesChannelId1]
```

**Arguments:**

* `salesChannelId` (optional, multiple): The sales channel ID(s) to pull translations for. If "default" or empty, the default provider is used.

**Options:**

* `--locales` / `-l` (multiple): Specify the locales to pull. If not provided, all relevant locales are pulled.

### Flush Translation Cache

Flushes the translation cache. This is useful after pulling new translations to make them visible in the storefront.

```bash
bin/console sw:cache:flush:translation
```

## API Endpoint

This plugin provides an API endpoint to trigger a translation update for specific sales channels. This is useful for integrating with webhooks from translation providers (e.g., when translations are completed).

* **URL:** `/api/_action/nlx/translation/update`
* **Method:** `POST`
* **Body (JSON):**
```json
{
"salesChannelIds": ["SALES_CHANNEL_ID_1", "SALES_CHANNEL_ID_2"]
}
```

## Asynchronous Processing

When the API endpoint is called, a message is dispatched to the Shopware message queue for each specified sales channel. A message handler then processes the queue and updates the translations for each sales channel asynchronously in the background.
56 changes: 56 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "netlogix/shopware-translation-bridge",
"description": "Load storefront translations from symfony/translation provider",
"license": "MIT",
"type": "shopware-platform-plugin",
"version": "v1.1.0",
"require": {
"php": ">8.3",
"shopware/storefront": ">=6.7.1.0",
"symfony/translation": ">7.0.0"
},
"require-dev": {
"carthage-software/mago": "*",
"ergebnis/composer-normalize": "*",
"frosh/shopware-rector": "*",
"phpunit/phpunit": "^13.0.0"
},
"autoload": {
"psr-4": {
"Netlogix\\ShopwareTranslationBridge\\": "src/"
}
},
"config": {
"allow-plugins": {
"carthage-software/mago": true,
"ergebnis/composer-normalize": true,
"symfony/runtime": false
}
},
"extra": {
"label": {
"de-DE": "Translation Bridge",
"en-GB": "Translation Bridge"
},
"shopware-plugin-class": "Netlogix\\ShopwareTranslationBridge\\ShopwareTranslationBridge"
},
"scripts": {
"apply-coding-standard": [
"@composer:normalize:fix",
"@rector:fix",
"@format:fix"
],
"composer:normalize": "@composer normalize --no-check-lock --dry-run",
"composer:normalize:fix": "@composer normalize --no-check-lock",
"format": "mago fmt --dry-run",
"format:fix": "mago fmt",
"lint": "mago lint",
"rector": "rector process --dry-run",
"rector:fix": "rector process",
"test": [
"@test:unit"
],
"test:coverage": "XDEBUG_MODE=coverage phpunit --testdox --coverage-html .coverage",
"test:unit": "phpunit --testdox --testsuite unit"
}
}
41 changes: 41 additions & 0 deletions mago.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Welcome to Mago!
# For full documentation, see https://mago.carthage.software/tools/overview
php-version = "8.4.0"

[source]
workspace = "."
paths = ["src/**/*.php", "tests/**/*.php"]
includes = ["vendor"]
excludes = []

[formatter]
preset = 'psr-12'
excludes = []
end-of-line = "Lf"
single-quote = true
empty-line-before-return = true

[linter]
integrations = ["symfony", "phpunit"]

[linter.rules]
ambiguous-function-call = { enabled = false }
literal-named-argument = { enabled = false }
halstead = { effort-threshold = 7000 }
final-controller = { enabled = false }
no-boolean-flag-parameter = { enabled = false }

[analyzer]
plugins = []
find-unused-definitions = true
find-unused-expressions = false
analyze-dead-code = false
memoize-properties = true
allow-possibly-undefined-array-keys = true
check-throws = false
check-missing-override = false
find-unused-parameters = false
strict-list-index-checks = false
no-boolean-literal-comparison = false
check-missing-type-hints = false
register-super-globals = true
42 changes: 42 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
cacheDirectory=".phpunit.cache"
backupGlobals="false"
colors="true"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnPhpunitDeprecations="true"
failOnPhpunitDeprecation="true"
failOnRisky="true"
failOnWarning="true">
<php>
<ini name="display_errors" value="1"/>
<ini name="error_reporting" value="-1"/>
<server name="APP_ENV" value="test" force="true"/>
<server name="SHELL_VERBOSITY" value="-1"/>
<server name="SYMFONY_PHPUNIT_REMOVE" value=""/>
<server name="SYMFONY_PHPUNIT_VERSION" value="12"/>
<server name="KERNEL_CLASS" value="App\Tests\TestKernel"/>
</php>

<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
</testsuite>
<!-- <testsuite name="integration">-->
<!-- <directory>tests/Integration</directory>-->
<!-- </testsuite>-->
</testsuites>

<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
24 changes: 24 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php


declare(strict_types=1);

use Frosh\Rector\Set\ShopwareSetList;
use Rector\Config\RectorConfig;
use Rector\ValueObject\PhpVersion;

return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/tests',
])
// uncomment to reach your current PHP version
->withPhpVersion(PhpVersion::PHP_83)
->withPhpSets(php83: true)
->withImportNames(removeUnusedImports: true)
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0)
->withSets([
ShopwareSetList::SHOPWARE_6_7_0,
]);
1 change: 1 addition & 0 deletions src/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/public
28 changes: 28 additions & 0 deletions src/Command/FlushTranslationCacheCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types = 1);

namespace Netlogix\ShopwareTranslationBridge\Command;

use Netlogix\ShopwareTranslationBridge\Core\Framework\Adapter\Translator\TranslationCacheInvalidationInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand('cache:translation:flush')]
class FlushTranslationCacheCommand extends Command
{
function __construct(
private readonly TranslationCacheInvalidationInterface $translationCacheInvalidation
) {
parent::__construct();
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->translationCacheInvalidation->invalidate(true);

return Command::SUCCESS;
}
}
Loading