diff --git a/README.md b/README.md
index e36f2e7..61251df 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ Please have a look at the [Web app manifests](https://developer.mozilla.org/en-U
Install the bundle with Composer:
```bash
-composer require --dev spomky-labs/phpwa
+composer require spomky-labs/phpwa
```
This project follows the [semantic versioning](http://semver.org/) strictly.
@@ -69,12 +69,22 @@ pwa:
'application/json': ['.json']
```
+### Using the Manifest
+
+The manifest can be used in your HTML pages with the following Twig function in the `
` section.
+It will automatically add the manifest your HTML pages and any other useful meta tags.
+
+```html
+{{ pwa() }}
+```
+
### Manifest Generation
-When the configuration is set, you can generate the manifest with the following command:
+On `dev` or `test` environment, the manifest will be generated for you.
+On `prod` environment, the manifest is compiled during the deployment with Asset Mapper.
```bash
-symfony console pwa:build
+symfony console asset-map:compile
```
By default, the manifest will be generated in the `public` directory with the name `/site.webmanifest`.
@@ -83,26 +93,12 @@ You can change the output file name and the output folder with the following con
```yaml
# config/packages/phpwa.yaml
pwa:
- manifest_filepath: '%kernel.project_dir%/public/sub-folder/pwa.json'
+ manifest_public_url: '/foo/pwa.json'
```
-### Using the Manifest
-
-The manifest can be used in your HTML pages with the following code in the `` section.
-In you customized the output filename or the public folder, please replace `/site.webmanifest` with the path to your manifest file.
-
-```html
-
-```
-
-### Manifest Icons
-
-The bundle is able to generate icons from a source image.
-This is applicable to the application icons, shortcut icons or Windows 10 widget icons members of the manifest.
-The icons must be square and the source image should be at best quality as possible.
-To process the icons, you should set an icon processor. The bundle provides a GD processor and an Imagick processor.
-Depending on your system, you may have to install one extension or the other.
+### Manifest Icons and Screenshots
+The bundle is able to link your assets to the manifest file.
Please note that the icons of a size greater than 1024px may be ignored by the browser.
```yaml
@@ -110,69 +106,36 @@ Please note that the icons of a size greater than 1024px may be ignored by the b
pwa:
image_processor: 'pwa.image_processor.gd' # or 'pwa.image_processor.imagick'
icons:
- - src: "%kernel.project_dir%/assets/images/logo.png"
- sizes: [48, 57, 60, 72, 76, 96, 114, 128, 144, 152, 180, 192, 256, 384, 512, 1024]
- format: 'webp'
- - src: "%kernel.project_dir%/assets/images/mask.png"
- sizes: [48, 57, 60, 72, 76, 96, 114, 128, 144, 152, 180, 192, 256, 384, 512, 1024]
- purpose: 'maskable'
- - src: "%kernel.project_dir%/assets/images/logo.svg"
+ - src: "images/logo.png"
+ sizes: [48, 96, 128, 256, 512, 1024]
+ - src: "images/logo.svg"
sizes: [0] # 0 means `any` size and is suitable for vector images
+ - src: "images/logo.svg"
+ purpose: 'maskable'
+ sizes: [0]
+ screenshots:
+ - src: "screenshots/android_dashboard.png"
+ platform: 'android'
+ label: "View of the dashboard on Android"
+ - "screenshots/android_feature1.png"
+ - "screenshots/android_feature2.png"
shortcuts:
- name: "Shortcut 1"
short_name: "shortcut-1"
url: "/shortcut1"
description: "Shortcut 1 description"
icons:
- - src: "%kernel.project_dir%/assets/images/shortcut1.png"
- sizes: [48, 72, 96, 128, 144, 192, 256, 384, 512]
- format: 'webp'
+ - src: "images/shortcut1.png"
+ sizes: [48, 96, 128, 256, 512, 1024]
- name: "Shortcut 2"
short_name: "shortcut-2"
url: "/shortcut2"
description: "Shortcut 2 description"
icons:
- - src: "%kernel.project_dir%/assets/images/shortcut2.png"
- sizes: [48, 72, 96, 128, 144, 192, 256, 384, 512]
- format: 'webp'
+ - src: "images/shortcut2.png"
+ sizes: [48, 96, 128, 256, 512, 1024]
```
-With the configuration above, the bundle will generate
-* 16 icons from the `logo.png` image. The icons will be converted from `png` to `webp`.
-* 16 icons from the `mask.png` image. The format will be `png` and the purpose will be `maskable`.
-* And 1 icon from the `logo.svg` image. The format will be `svg` and the size will be `any`.
-
-If the `format` member is present, the bundle will convert the image to the specified format.
-In the example above, the `logo.png` image will be converted to `webp`, but the `mask.png` image will not be converted.
-
-### Manifest Screenshots
-
-The bundle is able to generate screenshots from a source image.
-This is applicable to the application screenshots or Windows 10 widget screenshot members of the manifest.
-Similar to icons, the source image should be at best quality as possible.
-
-You can select a folder where the source images are stored.
-The bundle will automatically generate screenshots from all the images in the folder.
-
-```yaml
-# config/packages/phpwa.yaml
-pwa:
- image_processor: 'pwa.image_processor.gd' # or 'pwa.image_processor.imagick'
- screenshots:
- - src: "%kernel.project_dir%/assets/screenshots/narrow/"
- form_factor: "narrow"
- - src: "%kernel.project_dir%/assets/screenshots/wide/"
- form_factor: "wide"
- - src: "%kernel.project_dir%/assets/screenshots/android_dashboard.png"
- platform: 'android'
- format: 'webp'
- label: "View of the dashboard on Android"
-```
-
-The bundle will automatically generate screenshots from the source images and add additional information in the manifest
-such as the `sizes` and the `form_factor` (`wide` or `narrow`).
-The `format` parameter is optional. It indicates the format of the generated image. If not set, the format will be the same as the source image.
-
### Manifest Shortcuts
The `shortcuts` member may contain a list of action shortcuts that point to specific URLs in your application.
@@ -194,21 +157,28 @@ pwa:
## Service Worker
-The following command will generate a Service Worker in the `public` directory with the name `/sw.js`:
+The service worker is a JavaScript file that is executed by the browser.
+It can be served by Asset Mapper.
-```bash
-symfony console pwa:sw
-```
+```yaml
+# config/packages/phpwa.yaml
+pwa:
+ serviceworker: 'script/service-worker.js'
-If you want override the existing Service Worker, you can use the `--force` option:
+```
-```bash
-symfony console pwa:sw --force
+```yaml
+#The following configuration is similar
+pwa:
+ serviceworker:
+ src: 'script/service-worker.js'
+ dest: '/sw.js'
+ scope: '/'
```
Next, you have to register the Service Worker in your HTML pages with the following code in the `` section.
It can also be done in a JavaScript file such as `app.js`.
-In you customized the output filename or the public folder, please replace `sw.js` with the path to your Service Worker file.
+In you customized the destination filename, please replace `/sw.js` with the path to your Service Worker file.
```html
```
-The location of the Service Worker is important. It must be at the root of your application.
-In addition, the `serviceworker.src` member of the manifest must be set to the same location.
The `serviceworker.scope` member may be set to the same location or to a sub-folder.
Do not forget to update the `scope` member in the JS configuration.
-```yaml
-# config/packages/phpwa.yaml
-pwa:
- serviceworker:
- filepath: '%kernel.project_dir%/public/sub-folder/service-worker.js'
- src: '/sub-folder/service-worker.js'
- scope: '/'
-```
-
### Service Worker Configuration
The Service Worker uses Workbox and comes with predefined configuration and recipes.
diff --git a/composer.json b/composer.json
index 016911d..9ff1517 100644
--- a/composer.json
+++ b/composer.json
@@ -54,6 +54,7 @@
"symfony/mime": "^6.4|^7.0",
"symfony/panther": "^2.1",
"symfony/phpunit-bridge": "^6.4|^7.0",
+ "symfony/translation": "^7.0",
"symfony/yaml": "^6.4|^7.0",
"symplify/easy-coding-standard": "^12.0"
},
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index 6d179f1..0290fa5 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -63,18 +63,7 @@ private function setupShortcuts(ArrayNodeDefinition $node): void
->info('The description of the shortcut.')
->example('Awesome shortcut')
->end()
- ->scalarNode('url')
- ->isRequired()
- ->info('The URL of the shortcut.')
- ->example('https://example.com')
- ->end()
- ->arrayNode('url_params')
- ->treatFalseLike([])
- ->treatTrueLike([])
- ->treatNullLike([])
- ->prototype('variable')->end()
- ->info('The parameters of the action. Only used if the action is a route to a controller.')
- ->end()
+ ->append($this->getUrlNode('url', 'The URL of the shortcut.'))
->append($this->getIconsNode('The icons of the shortcut.'))
->end()
->end()
@@ -103,18 +92,7 @@ private function setupFileHandlers(ArrayNodeDefinition $node): void
->treatNullLike([])
->arrayPrototype()
->children()
- ->scalarNode('action')
- ->isRequired()
- ->info('The action to take.')
- ->example('/handle-audio-file')
- ->end()
- ->arrayNode('action_params')
- ->treatFalseLike([])
- ->treatTrueLike([])
- ->treatNullLike([])
- ->prototype('variable')->end()
- ->info('The parameters of the action. Only used if the action is a route to a controller.')
- ->end()
+ ->append($this->getUrlNode('action', 'The action to take.', ['/handle-audio-file']))
->arrayNode('accept')
->requiresAtLeastOneElement()
->useAttributeAsKey('name')
@@ -140,18 +118,9 @@ private function setupSharedTarget(ArrayNodeDefinition $node): void
->treatNullLike([])
->info('The share target of the application.')
->children()
- ->scalarNode('action')
- ->isRequired()
- ->info('The action of the share target.')
- ->example('/shared-content-receiver/')
- ->end()
- ->arrayNode('action_params')
- ->treatFalseLike([])
- ->treatTrueLike([])
- ->treatNullLike([])
- ->prototype('variable')->end()
- ->info('The parameters of the action. Only used if the action is a route to a controller.')
- ->end()
+ ->append(
+ $this->getUrlNode('action', 'The action of the share target.', ['/shared-content-receiver/'])
+ )
->scalarNode('method')
->info('The method of the share target.')
->example('GET')
@@ -211,18 +180,7 @@ private function setupProtocolHandlers(ArrayNodeDefinition $node): void
->info('The protocol of the handler.')
->example('web+jngl')
->end()
- ->scalarNode('url')
- ->isRequired()
- ->info('The URL of the handler.')
- ->example('/lookup?type=%s')
- ->end()
- ->arrayNode('url_params')
- ->treatFalseLike([])
- ->treatTrueLike([])
- ->treatNullLike([])
- ->prototype('variable')->end()
- ->info('The parameters of the action. Only used if the action is a route to a controller.')
- ->end()
+ ->append($this->getUrlNode('url', 'The URL of the handler.'))
->end()
->end()
->end()
@@ -269,11 +227,11 @@ private function setupRelatedApplications(ArrayNodeDefinition $node): void
->info('The platform of the application.')
->example('play')
->end()
- ->scalarNode('url')
- ->isRequired()
- ->info('The URL of the application.')
- ->example('https://play.google.com/store/apps/details?id=com.example.app1')
- ->end()
+ ->append(
+ $this->getUrlNode('url', 'The URL of the application.', [
+ 'https://play.google.com/store/apps/details?id=com.example.app1',
+ ])
+ )
->scalarNode('id')
->info('The ID of the application.')
->example('com.example.app1')
@@ -314,7 +272,7 @@ private function setupSimpleOptions(ArrayNodeDefinition $node): void
->end()
->end()
->scalarNode('manifest_public_url')
- ->defaultValue('/site.manifest')
+ ->defaultValue('/site.webmanifest')
->cannotBeEmpty()
->info('The public URL of the manifest file.')
->example('/site.manifest')
@@ -639,4 +597,39 @@ private function setupWidgets(ArrayNodeDefinition $node): void
->end()
->end();
}
+
+ /**
+ * @param array $examples
+ */
+ private function getUrlNode(string $name, string $info, null|array $examples = null): ArrayNodeDefinition
+ {
+ $treeBuilder = new TreeBuilder($name);
+ $node = $treeBuilder->getRootNode();
+ assert($node instanceof ArrayNodeDefinition);
+ $node
+ ->info($info)
+ ->beforeNormalization()
+ ->ifString()
+ ->then(static fn (string $v): array => [
+ 'path' => $v,
+ ])
+ ->end()
+ ->children()
+ ->scalarNode('path')
+ ->isRequired()
+ ->info('The URL of the shortcut.')
+ ->example($examples ?? ['https://example.com', 'app_action_route', '/do/action'])
+ ->end()
+ ->arrayNode('params')
+ ->treatFalseLike([])
+ ->treatTrueLike([])
+ ->treatNullLike([])
+ ->prototype('variable')->end()
+ ->info('The parameters of the action. Only used if the action is a route to a controller.')
+ ->end()
+ ->end()
+ ->end();
+
+ return $node;
+ }
}
diff --git a/src/Dto/FileHandler.php b/src/Dto/FileHandler.php
index 77b2240..47bb07a 100644
--- a/src/Dto/FileHandler.php
+++ b/src/Dto/FileHandler.php
@@ -4,17 +4,9 @@
namespace SpomkyLabs\PwaBundle\Dto;
-use Symfony\Component\Serializer\Attribute\SerializedName;
-
final class FileHandler
{
- public string $action;
-
- /**
- * @var array
- */
- #[SerializedName('action_params')]
- public array $actionParameters = [];
+ public Url $action;
/**
* @var array
diff --git a/src/Dto/Manifest.php b/src/Dto/Manifest.php
index 49b980e..a49b688 100644
--- a/src/Dto/Manifest.php
+++ b/src/Dto/Manifest.php
@@ -5,9 +5,12 @@
namespace SpomkyLabs\PwaBundle\Dto;
use Symfony\Component\Serializer\Attribute\SerializedName;
+use Symfony\Contracts\Translation\TranslatableInterface;
final class Manifest
{
+ use TranslatableTrait;
+
#[SerializedName('background_color')]
public null|string $backgroundColor = null;
@@ -108,4 +111,34 @@ final class Manifest
#[SerializedName('serviceworker')]
public null|ServiceWorker $serviceWorker = null;
+
+ /**
+ * @return array
+ */
+ public function getCategories(): array
+ {
+ return $this->provideTranslation($this->categories);
+ }
+
+ public function getDescription(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->description);
+ }
+
+ public function getName(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->name);
+ }
+
+ #[SerializedName('short_name')]
+ public function getShortName(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->shortName);
+ }
+
+ #[SerializedName('start_url')]
+ public function getStartUrl(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->startUrl);
+ }
}
diff --git a/src/Dto/ProtocolHandler.php b/src/Dto/ProtocolHandler.php
index 1d9ac7d..0e07856 100644
--- a/src/Dto/ProtocolHandler.php
+++ b/src/Dto/ProtocolHandler.php
@@ -4,17 +4,9 @@
namespace SpomkyLabs\PwaBundle\Dto;
-use Symfony\Component\Serializer\Attribute\SerializedName;
-
final class ProtocolHandler
{
public string $protocol;
- /**
- * @var array
- */
- #[SerializedName('url_params')]
- public array $urlParameters = [];
-
- public string $url;
+ public Url $url;
}
diff --git a/src/Dto/RelatedApplication.php b/src/Dto/RelatedApplication.php
index ba5ade4..37c9939 100644
--- a/src/Dto/RelatedApplication.php
+++ b/src/Dto/RelatedApplication.php
@@ -8,7 +8,7 @@ final class RelatedApplication
{
public string $platform;
- public string $url;
+ public Url $url;
public null|string $id = null;
}
diff --git a/src/Dto/Screenshot.php b/src/Dto/Screenshot.php
index 93415a5..b8b7a58 100644
--- a/src/Dto/Screenshot.php
+++ b/src/Dto/Screenshot.php
@@ -5,9 +5,12 @@
namespace SpomkyLabs\PwaBundle\Dto;
use Symfony\Component\Serializer\Attribute\SerializedName;
+use Symfony\Contracts\Translation\TranslatableInterface;
final class Screenshot
{
+ use TranslatableTrait;
+
public null|string $src = null;
public null|int $height = null;
@@ -22,4 +25,9 @@ final class Screenshot
public null|string $platform = null;
public null|string $format = null;
+
+ public function getLabel(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->label);
+ }
}
diff --git a/src/Dto/ShareTarget.php b/src/Dto/ShareTarget.php
index a669587..a85fc95 100644
--- a/src/Dto/ShareTarget.php
+++ b/src/Dto/ShareTarget.php
@@ -4,17 +4,9 @@
namespace SpomkyLabs\PwaBundle\Dto;
-use Symfony\Component\Serializer\Attribute\SerializedName;
-
final class ShareTarget
{
- public string $action;
-
- /**
- * @var array
- */
- #[SerializedName('action_params')]
- public array $actionParameters = [];
+ public Url $action;
public null|string $method = null;
diff --git a/src/Dto/ShareTargetParameters.php b/src/Dto/ShareTargetParameters.php
index 3d117d7..f43d21e 100644
--- a/src/Dto/ShareTargetParameters.php
+++ b/src/Dto/ShareTargetParameters.php
@@ -4,8 +4,12 @@
namespace SpomkyLabs\PwaBundle\Dto;
+use Symfony\Contracts\Translation\TranslatableInterface;
+
final class ShareTargetParameters
{
+ use TranslatableTrait;
+
public null|string $title = null;
public null|string $text = null;
@@ -16,4 +20,14 @@ final class ShareTargetParameters
* @var array
*/
public array $files = [];
+
+ public function getTitle(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->title);
+ }
+
+ public function getText(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->text);
+ }
}
diff --git a/src/Dto/Shortcut.php b/src/Dto/Shortcut.php
index cd9eaad..553c9ed 100644
--- a/src/Dto/Shortcut.php
+++ b/src/Dto/Shortcut.php
@@ -5,9 +5,12 @@
namespace SpomkyLabs\PwaBundle\Dto;
use Symfony\Component\Serializer\Attribute\SerializedName;
+use Symfony\Contracts\Translation\TranslatableInterface;
final class Shortcut
{
+ use TranslatableTrait;
+
public string $name;
#[SerializedName('short_name')]
@@ -15,16 +18,20 @@ final class Shortcut
public null|string $description = null;
- public string $url;
-
- /**
- * @var array
- */
- #[SerializedName('url_params')]
- public array $urlParameters = [];
+ public Url $url;
/**
* @var array
*/
public array $icons = [];
+
+ public function getName(): string|TranslatableInterface
+ {
+ $this->provideTranslation($this->name);
+ }
+
+ public function getDescription(): string|TranslatableInterface
+ {
+ return $this->provideTranslation($this->description);
+ }
}
diff --git a/src/Dto/TranslatableTrait.php b/src/Dto/TranslatableTrait.php
new file mode 100644
index 0000000..a4abf34
--- /dev/null
+++ b/src/Dto/TranslatableTrait.php
@@ -0,0 +1,29 @@
+ $data
+ *
+ * @return null|string|TranslatableInterface|array
+ */
+ public function provideTranslation(null|string|array $data): null|string|TranslatableInterface|array
+ {
+ if (! interface_exists(TranslatableInterface::class) || $data === null) {
+ return $data;
+ }
+ if (is_array($data)) {
+ return array_map(fn (string $value): TranslatableInterface => new TranslatableMessage($value), $data);
+ }
+
+ return new TranslatableMessage($data);
+ }
+}
diff --git a/src/Dto/Url.php b/src/Dto/Url.php
new file mode 100644
index 0000000..341cdf5
--- /dev/null
+++ b/src/Dto/Url.php
@@ -0,0 +1,15 @@
+
+ */
+ public array $params = [];
+}
diff --git a/src/Dto/Widget.php b/src/Dto/Widget.php
index 7ed39f9..84c1b82 100644
--- a/src/Dto/Widget.php
+++ b/src/Dto/Widget.php
@@ -5,6 +5,8 @@
namespace SpomkyLabs\PwaBundle\Dto;
use Symfony\Component\Serializer\Attribute\SerializedName;
+use Symfony\Component\Translation\TranslatableMessage;
+use Symfony\Contracts\Translation\TranslatableInterface;
final class Widget
{
@@ -41,4 +43,22 @@ final class Widget
public null|int $update = null;
public bool $multiple = true;
+
+ public function getName(): string|TranslatableInterface
+ {
+ if (! interface_exists(TranslatableInterface::class)) {
+ return $this->name;
+ }
+
+ return new TranslatableMessage($this->name);
+ }
+
+ public function getDescription(): string|TranslatableInterface
+ {
+ if (! interface_exists(TranslatableInterface::class) || $this->description === null) {
+ return $this->description;
+ }
+
+ return new TranslatableMessage($this->description);
+ }
}
diff --git a/src/Normalizer/ProtocolHandlerNormalizer.php b/src/Normalizer/ProtocolHandlerNormalizer.php
deleted file mode 100644
index 6db5c83..0000000
--- a/src/Normalizer/ProtocolHandlerNormalizer.php
+++ /dev/null
@@ -1,51 +0,0 @@
-url;
- if (! str_starts_with($url, '/') && ! filter_var($url, FILTER_VALIDATE_URL)) {
- $url = $this->router->generate($url, $object->urlParameters, $this->referenceType);
- }
-
- return [
- 'url' => $url,
- 'protocol' => $object->protocol,
- ];
- }
-
- public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
- {
- return $data instanceof ProtocolHandler;
- }
-
- /**
- * @return array
- */
- public function getSupportedTypes(?string $format): array
- {
- return [
- ProtocolHandler::class => true,
- ];
- }
-}
diff --git a/src/Normalizer/ShareTargetNormalizer.php b/src/Normalizer/ShareTargetNormalizer.php
deleted file mode 100644
index b593529..0000000
--- a/src/Normalizer/ShareTargetNormalizer.php
+++ /dev/null
@@ -1,59 +0,0 @@
-action;
- if (! str_starts_with($url, '/') && ! filter_var($url, FILTER_VALIDATE_URL)) {
- $url = $this->router->generate($url, $object->actionParameters, $this->referenceType);
- }
-
- $result = [
- 'action' => $url,
- 'method' => $object->method,
- 'enctype' => $object->enctype,
- 'params' => $object->params,
- ];
-
- $cleanup = static fn (array $data): array => array_filter(
- $data,
- static fn ($value) => ($value !== null && $value !== [])
- );
- return $cleanup($result);
- }
-
- public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
- {
- return $data instanceof ShareTarget;
- }
-
- /**
- * @return array
- */
- public function getSupportedTypes(?string $format): array
- {
- return [
- ShareTarget::class => true,
- ];
- }
-}
diff --git a/src/Normalizer/ShortcutNormalizer.php b/src/Normalizer/ShortcutNormalizer.php
index 89b6913..400d5d7 100644
--- a/src/Normalizer/ShortcutNormalizer.php
+++ b/src/Normalizer/ShortcutNormalizer.php
@@ -5,38 +5,24 @@
namespace SpomkyLabs\PwaBundle\Normalizer;
use SpomkyLabs\PwaBundle\Dto\Shortcut;
-use Symfony\Component\DependencyInjection\Attribute\Autowire;
-use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use function assert;
-use const FILTER_VALIDATE_URL;
final class ShortcutNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
- public function __construct(
- private readonly RouterInterface $router,
- #[Autowire('%spomky_labs_pwa.routes.reference_type%')]
- private readonly int $referenceType,
- ) {
- }
-
public function normalize(mixed $object, string $format = null, array $context = []): array
{
assert($object instanceof Shortcut);
- $url = $object->url;
- if (! str_starts_with($url, '/') && ! filter_var($url, FILTER_VALIDATE_URL)) {
- $url = $this->router->generate($url, $object->urlParameters, $this->referenceType);
- }
$result = [
'name' => $object->name,
'short_name' => $object->shortName,
'description' => $object->description,
- 'url' => $url,
+ 'url' => $this->normalizer->normalize($object->url, $format, $context),
'icons' => $this->normalizer->normalize($object->icons, $format, $context),
];
diff --git a/src/Normalizer/FileHandlerNormalizer.php b/src/Normalizer/UrlNormalizer.php
similarity index 50%
rename from src/Normalizer/FileHandlerNormalizer.php
rename to src/Normalizer/UrlNormalizer.php
index c4ec167..0e563f9 100644
--- a/src/Normalizer/FileHandlerNormalizer.php
+++ b/src/Normalizer/UrlNormalizer.php
@@ -4,39 +4,40 @@
namespace SpomkyLabs\PwaBundle\Normalizer;
-use SpomkyLabs\PwaBundle\Dto\FileHandler;
+use SpomkyLabs\PwaBundle\Dto\Url;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Routing\RouterInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
+use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use function assert;
use const FILTER_VALIDATE_URL;
-final readonly class FileHandlerNormalizer implements NormalizerInterface
+final class UrlNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
+ use NormalizerAwareTrait;
+
public function __construct(
- private RouterInterface $router,
+ private readonly RouterInterface $router,
#[Autowire('%spomky_labs_pwa.routes.reference_type%')]
- private int $referenceType,
+ private readonly int $referenceType,
) {
}
- public function normalize(mixed $object, string $format = null, array $context = []): array
+ public function normalize(mixed $object, string $format = null, array $context = []): ?string
{
- assert($object instanceof FileHandler);
- $url = $object->action;
- if (! str_starts_with($url, '/') && ! filter_var($url, FILTER_VALIDATE_URL)) {
- $url = $this->router->generate($url, $object->actionParameters, $this->referenceType);
+ assert($object instanceof Url);
+
+ if (! str_starts_with($object->path, '/') && ! filter_var($object->path, FILTER_VALIDATE_URL)) {
+ return $this->router->generate($object->path, $object->params, $this->referenceType);
}
- return [
- 'action' => $url,
- 'accept' => $object->accept,
- ];
+ return $object->path;
}
public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
{
- return $data instanceof FileHandler;
+ return $data instanceof Url;
}
/**
@@ -45,7 +46,7 @@ public function supportsNormalization(mixed $data, string $format = null, array
public function getSupportedTypes(?string $format): array
{
return [
- FileHandler::class => true,
+ Url::class => true,
];
}
}
diff --git a/tests/Functional/ManifestFileDevServerTest.php b/tests/Functional/ManifestFileDevServerTest.php
index 2479ebc..bdf492b 100644
--- a/tests/Functional/ManifestFileDevServerTest.php
+++ b/tests/Functional/ManifestFileDevServerTest.php
@@ -19,10 +19,11 @@ public static function aScreenshotIsCorrectlyTake(): void
$client = static::createClient();
// When
- $client->request('GET', '/site.manifest');
+ $client->request('GET', '/site.webmanifest');
// Then
static::assertResponseIsSuccessful();
static::assertResponseHeaderSame('Content-Type', 'application/manifest+json');
+ dump($client->getResponse()->getContent());
}
}
diff --git a/tests/config.php b/tests/config.php
index befd97e..3bd2f61 100644
--- a/tests/config.php
+++ b/tests/config.php
@@ -31,19 +31,27 @@
'utf8' => true,
'resource' => '%kernel.project_dir%/tests/routes.php',
],
+ 'default_locale' => 'en',
+ 'translator' => [
+ 'enabled' => true,
+ 'default_path' => '%kernel.project_dir%/tests/translations',
+ 'fallbacks' => ['en'],
+ ],
]);
$container->extension('pwa', [
'image_processor' => DummyImageProcessor::class,
'background_color' => 'red',
- 'categories' => ['books', 'education', 'medical'],
- 'description' => 'Awesome application that will help you achieve your dreams.',
+ 'categories' => ['pwa.categories.0', 'pwa.categories.1', 'pwa.categories.2'],
+ 'description' => 'pwa.description',
'display' => 'standalone',
'display_override' => ['fullscreen', 'minimal-ui'],
'file_handlers' => [
[
- 'action' => 'audio_file_handler',
- 'action_params' => [
- 'param1' => 'audio',
+ 'action' => [
+ 'path' => 'audio_file_handler',
+ 'params' => [
+ 'param1' => 'audio',
+ ],
],
'accept' => [
'audio/wav' => ['.wav'],
@@ -76,7 +84,7 @@
'sizes' => [0],
],
],
- 'id' => '?homescreen=1',
+ 'id' => '/?homescreen=1',
'launch_handler' => [
'client_mode' => ['focus-existing', 'auto'],
],
@@ -84,8 +92,8 @@
'prefer_related_applications' => true,
'dir' => 'rtl',
'lang' => 'ar',
- 'name' => 'تطبيق رائع',
- 'short_name' => 'رائع',
+ 'name' => 'pwa.name',
+ 'short_name' => 'pwa.short_name',
'protocol_handlers' => [
[
'protocol' => 'web+jngl',
@@ -111,15 +119,22 @@
'url' => 'https://apps.microsoft.com/store/detail/example-app1/id123456789',
],
],
- 'scope' => '/app/',
- 'start_url' => 'https://example.com',
+ 'scope' => '/',
+ 'start_url' => 'pwa.start_url',
'theme_color' => 'red',
- 'screenshots' => ['pwa/screenshots/360x800.svg'],
+ 'screenshots' => [
+ [
+ 'src' => 'pwa/screenshots/360x800.svg',
+ 'label' => 'pwa.screenshots.0',
+ ],
+ ],
'share_target' => [
- 'action' => 'shared_content_receiver',
- 'action_params' => [
- 'param1' => 'value1',
- 'param2' => 'value2',
+ 'action' => [
+ 'path' => 'shared_content_receiver',
+ 'params' => [
+ 'param1' => 'value1',
+ 'param2' => 'value2',
+ ],
],
'method' => 'GET',
'params' => [
@@ -131,9 +146,11 @@
'shortcuts' => [
[
'name' => "Today's agenda",
- 'url' => 'agenda',
- 'url_params' => [
- 'date' => 'today',
+ 'url' => [
+ 'path' => 'agenda',
+ 'params' => [
+ 'date' => 'today',
+ ],
],
'description' => 'List of events planned for today',
],
@@ -174,7 +191,7 @@
'description' => 'widget to control the PWAmp music player',
'tag' => 'pwamp',
'template' => 'pwamp-template',
- 'ms_ac_template' => 'widgets/mini-player-template.json',
+ 'ms_ac_template' => '/widgets/mini-player-template.json',
'data' => 'widgets/mini-player-data.json',
'type' => 'application/json',
'screenshots' => [
diff --git a/tests/translations/messages.en.yaml b/tests/translations/messages.en.yaml
new file mode 100644
index 0000000..3eda119
--- /dev/null
+++ b/tests/translations/messages.en.yaml
@@ -0,0 +1,11 @@
+pwa:
+ name: 'Weather App'
+ short_name: 'weather-app'
+ start_url: './'
+ description: 'A simple weather app built as a PWA with Symfony'
+ categories:
+ - 'utility'
+ - 'productivity'
+ - 'weather'
+ screenshots:
+ - 'This is a screenshot of the app'
\ No newline at end of file