Skip to content

Commit 2b12fb6

Browse files
author
shopwareBot
committed
[create-pull-request] automated change
1 parent 4296168 commit 2b12fb6

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
title: Cache layer for navigation loader
3+
date: 2025-08-19
4+
area: framework & discovery
5+
tags: [performance, cache, categories]
6+
---
7+
8+
# Cache layer for navigation loader
9+
10+
::: info
11+
This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository.
12+
You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2025-08-19-cache-layer-for-navigation-loader.md)
13+
:::
14+
15+
## Context
16+
We see in multiple performance analysis that the navigation loader can be a bottleneck for the performance of the storefront, especially with a huge number of categories in the first levels of the category tree.
17+
The navigation loader is responsible for loading the categories and their children, which are then used to render the navigation in the storefront.
18+
This process can be quite expensive, not just because of the query time on the DB, but also for hydrating the entities into PHP objects.
19+
The other part with a huge performance impact is the rendering performance on twig side for the many nested categories, however that is not part of this ADR.
20+
21+
The navigation loader is used in the storefront for every header and every listing page (when they use the sidebar navigation CMS element).
22+
However, the data that is loaded is always the same for the same sales channel, because we always show/load the category tree for the main navigation of the sales channel to the depth that is configured in the sales channel config.
23+
24+
Adding support for ESI for the header and footer addresses the same fundamental performance issue, however it does not solve the problem for the listing pages, where the sidebar navigation CMS element is used.
25+
Additionally, for ESI to be effective you would need a reverse proxy that supports ESI, and you need to disable compression your responses from your webserver, which could increase your infrastructure costs. So ESI is not a viable solution for every use case.
26+
27+
## Decision
28+
We will implement a cache layer for the navigation loader to improve the performance of the storefront.
29+
To not only safe the query time, but also the hydration time, we will store the categories as PHP serialized objects. That should be fine, because when the structure of the PHP objects changes, that means there is a new platform version, in which case the cache needs to be cleared anyway.
30+
Also in order to be most effective and not store too much data, we will only cache the category tree for the main navigation for every sales channel and up to the depths that is configured in the sales channel, because that info is loaded on every header and every listing page (when they use the sidebar navigation CMS element).
31+
We use the `CacheCompressor` to compress the serialized data before storing it in the cache, which should reduce the size of the cache entries significantly, however it adds some more processing time when reading and writing the cache entries.
32+
33+
This cache layer will work complementary to ESI, because ESI would also cache the rendered HTML of the navigation, the performance impact of ESI will be faster, but category information for the sidebar navigation is still loaded on every listing page, so this change is still beneficial in regard to performance, even when you use ESI.
34+
35+
## Consequences
36+
* The loaded categories for the main navigation will be cached to the level defined in the sales channel config.
37+
* Additional categories that might be loaded because e.g. the currently active category is below the configured depth will be loaded dynamically per request and merged with the default categories.
38+
* The cache needs to be invalidated whenever a category is written or deleted. We use immediate cache invalidation for that, so that the behaviour is as before, even with deactivated HTTP-Cache.
39+
* We encode the information about the `salesChannelId`, `language`, `root category id` and `depth` in the cache key, so that we can cache the categories for different sales channels and languages.
40+
* We will add a `CategoryLevelLoaderCacheKeyEvent` so that plugins can modify the cache key if they dynamically influence which categories should be loaded/shown.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
title: Adding a country-agnostic language layer
3+
date: 2025-09-01
4+
area: discovery
5+
tags: [administration, storefront, plugin, app, languages, language-pack, translations, crowdin]
6+
---
7+
8+
# Adding a country-agnostic language layer
9+
10+
::: info
11+
This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository.
12+
You can find the original version [here](https://github.com/shopware/shopware/blob/trunk/adr/2025-09-01-adding-a-country-agnostic-language-layer.md)
13+
:::
14+
15+
## Context
16+
Currently, Shopware’s language system uses a specific language locale (e.g. `de-DE` for German in Germany) for its translation, while falling back to another specific locale (e.g. `en-GB` for English in Great Britain).
17+
18+
This works well, but it is quite maintenance-heavy. If you use multiple versions or dialects of a language, such as British and American English, it will result in a lot of duplicated snippets.
19+
20+
### Some facts and figures
21+
- Shopware has approximately 12,000 snippets.
22+
- By default, the US translation is an automated copy of the GB translation, which is later adjusted by our community via Crowdin.
23+
- When running them through a comparison tool, we find about 70 differences. This means that roughly 99.5% of our American snippets are identical to the British ones.
24+
25+
### Current structure
26+
27+
```mermaid
28+
flowchart LR
29+
subgraph "Specific language"
30+
deDE("<strong>de-DE</strong><br/><em>German in Germany</em>")
31+
deAT("<strong>de-AT</strong><br/><em>German in Austria</em>")
32+
esES("<strong>es-ES</strong><br/><em>Spanish in Spain</em>")
33+
esAR("<strong>es-AR</strong><br/><em>Spanish in Argentina</em>")
34+
enUS("<strong>en-US</strong><br/><em>English in the USA</em>")
35+
end
36+
subgraph "System default language"
37+
enGB("<strong>en-GB</strong><br/><em>English in Great Britain</em>")
38+
end
39+
40+
deDE-->enGB
41+
deAT-->enGB
42+
43+
esES-->enGB
44+
esAR-->enGB
45+
46+
enUS-->enGB
47+
```
48+
49+
## Decision
50+
Another layer containing a country-agnostic language (e.g., `en` for both `en-GB` and `en-US`) will provide an additional fallback layer. This means dialects are now treated as patch files. In Shopware, `en-GB` will be renamed to `en`, as you have to select a base dialect, retaining its 12,000 snippets, while `en-US` will shrink to around 70 snippets. This also eliminates the need for tools to initially copy-paste or pre-translate from "English to English," which, of course, incurs costs.
51+
52+
## Consequences
53+
- Every additional dialect’s snippet file (e.g., `en-US` for `en`) will shrink from around 12,000 entries to likely fewer than 100.
54+
- We will also provide an additional layer to maximize compatibility with existing installations. This means that `en-GB` will still be available as a fallback for other languages like `de-DE`, but `en` will be the primary fallback for everything.
55+
- We will also provide an additional layer to ensure maximum compatibility with existing translations (e.g. via extensions). This means `en-GB` will remain available as a fallback for other languages such as `de-DE` and `de`, while `en` will serve as the final fallback.
56+
- Translations provided via the community (Crowdin) will show a lower coverage percentage, but will more accurately reflect the actual differences between `en-US` and `en`.
57+
- In Crowdin and in our product, the base language is set declaratively, so we will inform the community that British English is our base language, and we simply refer to it as `en`.
58+
- All snippet files in Shopware’s core will be renamed to the country-agnostic language
59+
- `en-GB.json` -> `en.json`, `messages.en-GB.base.json` -> `messages.en.base.json` and so on.
60+
- Similarly: `de-DE` -> `de`
61+
- The community translations / language pack will update their locales to their country-agnostic versions as well (e.g., `es-ES` -> `es`).
62+
- The system’s default language or active language handling will remain unchanged.
63+
- Plugins and apps do not require immediate adjustments, as the previous structure will continue to function as before as well.
64+
65+
### New structure
66+
67+
```mermaid
68+
flowchart LR
69+
subgraph "Specific language (German)"
70+
deDE("<strong>de-DE</strong><br/><em>German in Germany</em>")
71+
deAT("<strong>de-AT</strong><br/><em>German in Austria</em>")
72+
de("<strong>de</strong><br/><em>German</em>")
73+
end
74+
subgraph "Specific language (Spanish)"
75+
esES("<strong>es-ES</strong><br/><em>Spanish in Spain</em>")
76+
esAR("<strong>es-AR</strong><br/><em>Spanish in Argentina</em>")
77+
es("<strong>es</strong><br/><em>Spanish</em>")
78+
end
79+
subgraph "Specific language (English)"
80+
enUS("<strong>en-US</strong><br/><em>English in the USA</em>")
81+
end
82+
subgraph "Fallback (English)"
83+
enGB("<strong>en-GB</strong><br/><em>English in Great Britain</em>")
84+
en("<strong>en</strong><br/><em>English</em>")
85+
end
86+
87+
deDE-->de
88+
deAT-->de
89+
de-->enGB
90+
91+
esES-->es
92+
esAR-->es
93+
es-->enGB
94+
95+
enUS-->en
96+
enGB-->en
97+
98+
style de color:#6f6,stroke:#6f6
99+
style es color:#6f6,stroke:#6f6
100+
style en color:#6f6,stroke:#6f6
101+
102+
style enGB stroke-dasharray: 5
103+
```

0 commit comments

Comments
 (0)