@plone/volto 19.0.0-alpha.7 → 19.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/cypress/docker/prefixed-rules.yml +26 -0
  3. package/cypress/docker/prefixed.yml +24 -0
  4. package/locales/ca/LC_MESSAGES/volto.po +1 -1
  5. package/locales/de/LC_MESSAGES/volto.po +1 -1
  6. package/locales/en/LC_MESSAGES/volto.po +1 -1
  7. package/locales/es/LC_MESSAGES/volto.po +1 -1
  8. package/locales/eu/LC_MESSAGES/volto.po +1 -1
  9. package/locales/fi/LC_MESSAGES/volto.po +1 -1
  10. package/locales/fr/LC_MESSAGES/volto.po +1 -1
  11. package/locales/hi/LC_MESSAGES/volto.po +1 -1
  12. package/locales/it/LC_MESSAGES/volto.po +1 -1
  13. package/locales/ja/LC_MESSAGES/volto.po +1 -1
  14. package/locales/nl/LC_MESSAGES/volto.po +1 -1
  15. package/locales/pt/LC_MESSAGES/volto.po +1 -1
  16. package/locales/pt_BR/LC_MESSAGES/volto.po +1 -1
  17. package/locales/ro/LC_MESSAGES/volto.po +1 -1
  18. package/locales/ru/LC_MESSAGES/volto.po +1 -1
  19. package/locales/volto.pot +1 -1
  20. package/locales/zh_CN/LC_MESSAGES/volto.po +1 -1
  21. package/package.json +10 -9
  22. package/razzle.config.js +16 -0
  23. package/src/components/manage/Blocks/Listing/ImageGallery.jsx +6 -4
  24. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +3 -3
  25. package/src/components/theme/Image/Image.jsx +11 -8
  26. package/src/components/theme/LanguageSelector/LanguageSelector.tsx +89 -0
  27. package/src/components/theme/RequestTimeout/RequestTimeout.jsx +1 -1
  28. package/src/config/index.js +12 -10
  29. package/src/config/server.js +0 -2
  30. package/src/express-middleware/devproxy.js +14 -4
  31. package/src/helpers/Api/APIResourceWithAuth.js +8 -3
  32. package/src/helpers/Api/Api.js +7 -4
  33. package/src/helpers/AsyncConnect/ssr.js +4 -1
  34. package/src/helpers/Html/Html.jsx +13 -4
  35. package/src/helpers/Sitemap/Sitemap.js +4 -4
  36. package/src/helpers/Url/Url.js +32 -2
  37. package/src/helpers/Url/Url.test.js +62 -0
  38. package/src/server.jsx +40 -8
  39. package/src/start-client.jsx +9 -2
  40. package/src/start-server.js +9 -3
  41. package/tsconfig.json +3 -4
  42. package/types/components/theme/LanguageSelector/LanguageSelector.d.ts +3 -10
  43. package/types/helpers/Url/Url.d.ts +14 -0
  44. package/types/reducers/index.d.ts +1 -0
  45. package/types/start-client.d.ts +0 -1
  46. package/package-why.json +0 -34
  47. package/src/components/theme/LanguageSelector/LanguageSelector.jsx +0 -79
  48. /package/src/components/theme/LanguageSelector/{LanguageSelector.test.jsx → LanguageSelector.test.tsx} +0 -0
package/CHANGELOG.md CHANGED
@@ -17,6 +17,28 @@ myst:
17
17
 
18
18
  <!-- towncrier release notes start -->
19
19
 
20
+ ## 19.0.0-alpha.9 (2025-10-22)
21
+
22
+ ### Feature
23
+
24
+ - Add Cypress for a subpath. @wesleybl [#6976](https://github.com/plone/volto/issues/6976)
25
+ - Serve API requests from a subpath. @davisagli [#7326](https://github.com/plone/volto/issues/7326)
26
+ - Add option to serve a Volto site on a subpath using the `RAZZLE_SUBPATH_PREFIX` environment variable. @nileshgulia1, @wesleybl, @davisagli
27
+
28
+ ### Internal
29
+
30
+ - Expires the cache if the root `package.json` is changed. @wesleybl [#7536](https://github.com/plone/volto/issues/7536)
31
+ - Move to `dependencies` some bad categorized dependencies in `devDependencies`. @sneridagh
32
+ - Run Cookieplone acceptance test with Node 24. @davisagli
33
+
34
+ ## 19.0.0-alpha.8 (2025-10-22)
35
+
36
+ ### Internal
37
+
38
+ - Modernize and update `tsconfig.json` settings for core. @sneridagh [#7531.1](https://github.com/plone/volto/issues/7531.1)
39
+ - Refactor `LanguageSelector` into TypeScript. @sneridagh [#7531.2](https://github.com/plone/volto/issues/7531.2)
40
+ - Re-enable scripts pnpm build. Remove why. @sneridagh [#7532](https://github.com/plone/volto/issues/7532)
41
+
20
42
  ## 19.0.0-alpha.7 (2025-10-21)
21
43
 
22
44
  ### Breaking
@@ -0,0 +1,26 @@
1
+ http:
2
+ routers:
3
+ frontend:
4
+ rule: "Host(`localhost`) && PathPrefix(`/foo`)"
5
+ service: frontend
6
+ backend:
7
+ rule: "Host(`localhost`) && PathPrefix(`/foo/++api++`)"
8
+ service: backend
9
+ middlewares:
10
+ - backend
11
+
12
+ middlewares:
13
+ backend:
14
+ replacePathRegex:
15
+ regex: "^/foo/\\+\\+api\\+\\+($|/.*)"
16
+ replacement: "/VirtualHostBase/http/localhost/plone/++api++/VirtualHostRoot/_vh_foo$1"
17
+
18
+ services:
19
+ frontend:
20
+ loadBalancer:
21
+ servers:
22
+ - url: "http://host.docker.internal:3000"
23
+ backend:
24
+ loadBalancer:
25
+ servers:
26
+ - url: "http://host.docker.internal:55001"
@@ -0,0 +1,24 @@
1
+ version: "3.7"
2
+
3
+ services:
4
+
5
+ proxy:
6
+ image: traefik:v2.8
7
+ command:
8
+ - "--api.insecure=true"
9
+ - "--providers.docker=true"
10
+ # - "--providers.docker.exposedbydefault=false"
11
+ - "--providers.file=true"
12
+ - "--providers.file.filename=/etc/traefik/rules.yml"
13
+ - "--entrypoints.web.address=:80"
14
+ - "--api.insecure=true"
15
+ # - "--accesslog=true"
16
+ # - "--log.level=DEBUG"
17
+ ports:
18
+ - 80:80
19
+ - "8888:8080"
20
+ volumes:
21
+ - /var/run/docker.sock:/var/run/docker.sock
22
+ - ./prefixed-rules.yml:/etc/traefik/rules.yml
23
+ extra_hosts:
24
+ - host.docker.internal:host-gateway
@@ -2474,7 +2474,7 @@ msgstr ""
2474
2474
  msgid "No broken relations found."
2475
2475
  msgstr ""
2476
2476
 
2477
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2477
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2478
2478
  #: components/theme/RequestTimeout/RequestTimeout
2479
2479
  msgid "No connection to the server"
2480
2480
  msgstr ""
@@ -2473,7 +2473,7 @@ msgstr "Keine Add-ons installiert."
2473
2473
  msgid "No broken relations found."
2474
2474
  msgstr "Alle Relationen sind OK."
2475
2475
 
2476
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2476
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2477
2477
  #: components/theme/RequestTimeout/RequestTimeout
2478
2478
  msgid "No connection to the server"
2479
2479
  msgstr "Keine Verbindung zum Server"
@@ -2468,7 +2468,7 @@ msgstr ""
2468
2468
  msgid "No broken relations found."
2469
2469
  msgstr ""
2470
2470
 
2471
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2471
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2472
2472
  #: components/theme/RequestTimeout/RequestTimeout
2473
2473
  msgid "No connection to the server"
2474
2474
  msgstr ""
@@ -2475,7 +2475,7 @@ msgstr "No se han encontrado complementos"
2475
2475
  msgid "No broken relations found."
2476
2476
  msgstr "No se encontraron relaciones rotas."
2477
2477
 
2478
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2478
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2479
2479
  #: components/theme/RequestTimeout/RequestTimeout
2480
2480
  msgid "No connection to the server"
2481
2481
  msgstr "No hay conexión con el servidor"
@@ -2475,7 +2475,7 @@ msgstr "Ez da gehigarririk aurkitu"
2475
2475
  msgid "No broken relations found."
2476
2476
  msgstr ""
2477
2477
 
2478
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2478
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2479
2479
  #: components/theme/RequestTimeout/RequestTimeout
2480
2480
  msgid "No connection to the server"
2481
2481
  msgstr "Ez dago konexiorik zerbitzariarekin"
@@ -2473,7 +2473,7 @@ msgstr "Lisäosia ei löytynyt"
2473
2473
  msgid "No broken relations found."
2474
2474
  msgstr ""
2475
2475
 
2476
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2476
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2477
2477
  #: components/theme/RequestTimeout/RequestTimeout
2478
2478
  msgid "No connection to the server"
2479
2479
  msgstr "Ei yhteyttä palvelimeen"
@@ -2475,7 +2475,7 @@ msgstr "Aucun module trouvé"
2475
2475
  msgid "No broken relations found."
2476
2476
  msgstr ""
2477
2477
 
2478
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2478
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2479
2479
  #: components/theme/RequestTimeout/RequestTimeout
2480
2480
  msgid "No connection to the server"
2481
2481
  msgstr "Aucune connexion avec le serveur"
@@ -2468,7 +2468,7 @@ msgstr "कोई ऐड-ऑन नहीं मिला"
2468
2468
  msgid "No broken relations found."
2469
2469
  msgstr "कोई टूटी हुई संबंध नहीं मिला।"
2470
2470
 
2471
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2471
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2472
2472
  #: components/theme/RequestTimeout/RequestTimeout
2473
2473
  msgid "No connection to the server"
2474
2474
  msgstr "सर्वर से कोई कनेक्शन नहीं है"
@@ -2468,7 +2468,7 @@ msgstr "Nessun addon trovato"
2468
2468
  msgid "No broken relations found."
2469
2469
  msgstr "Nessuna relazione corrotta trovata."
2470
2470
 
2471
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2471
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2472
2472
  #: components/theme/RequestTimeout/RequestTimeout
2473
2473
  msgid "No connection to the server"
2474
2474
  msgstr "Non c'è connessione con il server, a causa di un timeout o di problemi di connessione di rete del tuo dispositivo."
@@ -2473,7 +2473,7 @@ msgstr ""
2473
2473
  msgid "No broken relations found."
2474
2474
  msgstr ""
2475
2475
 
2476
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2476
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2477
2477
  #: components/theme/RequestTimeout/RequestTimeout
2478
2478
  msgid "No connection to the server"
2479
2479
  msgstr ""
@@ -2472,7 +2472,7 @@ msgstr "Geen modules gevonden"
2472
2472
  msgid "No broken relations found."
2473
2473
  msgstr "Geen gebroken relaties gevonden."
2474
2474
 
2475
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2475
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2476
2476
  #: components/theme/RequestTimeout/RequestTimeout
2477
2477
  msgid "No connection to the server"
2478
2478
  msgstr "Er is geen verbinding naar de server, wegens een timeout of geen netwerkverbinding."
@@ -2473,7 +2473,7 @@ msgstr ""
2473
2473
  msgid "No broken relations found."
2474
2474
  msgstr ""
2475
2475
 
2476
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2476
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2477
2477
  #: components/theme/RequestTimeout/RequestTimeout
2478
2478
  msgid "No connection to the server"
2479
2479
  msgstr ""
@@ -2474,7 +2474,7 @@ msgstr "Nenhum complemento encontrado"
2474
2474
  msgid "No broken relations found."
2475
2475
  msgstr "Nenhum relacionamento rompido foi encontrado"
2476
2476
 
2477
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2477
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2478
2478
  #: components/theme/RequestTimeout/RequestTimeout
2479
2479
  msgid "No connection to the server"
2480
2480
  msgstr "Sem conexão ao servidor"
@@ -2474,7 +2474,7 @@ msgstr "Nu s-au găsit addons"
2474
2474
  msgid "No broken relations found."
2475
2475
  msgstr "Nu s-au găsit relații rupte."
2476
2476
 
2477
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2477
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2478
2478
  #: components/theme/RequestTimeout/RequestTimeout
2479
2479
  msgid "No connection to the server"
2480
2480
  msgstr "Nu există conexiunea la server, din cauza unui timeout sau a unei conexiuni întrerupte."
@@ -2473,7 +2473,7 @@ msgstr "Дополнения не найдены"
2473
2473
  msgid "No broken relations found."
2474
2474
  msgstr "Нарушенные связи не выявлены."
2475
2475
 
2476
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2476
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2477
2477
  #: components/theme/RequestTimeout/RequestTimeout
2478
2478
  msgid "No connection to the server"
2479
2479
  msgstr "Нет соединения с сервером"
package/locales/volto.pot CHANGED
@@ -2470,7 +2470,7 @@ msgstr ""
2470
2470
  msgid "No broken relations found."
2471
2471
  msgstr ""
2472
2472
 
2473
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2473
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2474
2474
  #: components/theme/RequestTimeout/RequestTimeout
2475
2475
  msgid "No connection to the server"
2476
2476
  msgstr ""
@@ -2474,7 +2474,7 @@ msgstr "没有找到附件"
2474
2474
  msgid "No broken relations found."
2475
2475
  msgstr ""
2476
2476
 
2477
- #. Default: "There is no connection to the server, due to a timeout o no network connection."
2477
+ #. Default: "There is no connection to the server, due to a timeout or no network connection."
2478
2478
  #: components/theme/RequestTimeout/RequestTimeout
2479
2479
  msgid "No connection to the server"
2480
2480
  msgstr "与服务器无连接"
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "19.0.0-alpha.7",
12
+ "version": "19.0.0-alpha.9",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -147,6 +147,9 @@
147
147
  "node": "^22 || ^24"
148
148
  },
149
149
  "dependencies": {
150
+ "@dnd-kit/core": "6.0.8",
151
+ "@dnd-kit/sortable": "7.0.2",
152
+ "@dnd-kit/utilities": "3.2.2",
150
153
  "@loadable/component": "5.14.1",
151
154
  "@loadable/server": "5.14.0",
152
155
  "@redux-devtools/extension": "^3.3.0",
@@ -159,6 +162,7 @@
159
162
  "diff": "3.5.0",
160
163
  "express": "4.19.2",
161
164
  "filesize": "6",
165
+ "full-icu": "1.4.0",
162
166
  "github-slugger": "1.4.0",
163
167
  "history": "4.10.1",
164
168
  "hoist-non-react-statics": "3.3.2",
@@ -236,9 +240,9 @@
236
240
  "url": "^0.11.3",
237
241
  "use-deep-compare-effect": "1.8.1",
238
242
  "uuid": "^8.3.2",
239
- "@plone/registry": "3.0.0-alpha.6",
240
243
  "@plone/scripts": "4.0.0-alpha.3",
241
- "@plone/volto-slate": "19.0.0-alpha.6"
244
+ "@plone/volto-slate": "19.0.0-alpha.6",
245
+ "@plone/registry": "3.0.0-alpha.6"
242
246
  },
243
247
  "devDependencies": {
244
248
  "@babel/core": "^7.0.0",
@@ -251,9 +255,6 @@
251
255
  "@babel/plugin-syntax-export-namespace-from": "7.8.3",
252
256
  "@babel/runtime": "7.20.6",
253
257
  "@babel/types": "7.20.5",
254
- "@dnd-kit/core": "6.0.8",
255
- "@dnd-kit/sortable": "7.0.2",
256
- "@dnd-kit/utilities": "3.2.2",
257
258
  "@fiverr/afterbuild-webpack-plugin": "^1.0.0",
258
259
  "@jest/globals": "^29.7.0",
259
260
  "@loadable/babel-plugin": "5.13.2",
@@ -279,8 +280,10 @@
279
280
  "@types/node": "^22.13.0",
280
281
  "@types/react": "^18",
281
282
  "@types/react-dom": "^18",
283
+ "@types/react-intl-redux": "^0.1.19",
282
284
  "@types/react-router-dom": "^5.3.3",
283
285
  "@types/react-test-renderer": "18.0.7",
286
+ "@types/redux-mock-store": "^1.5.0",
284
287
  "@types/uuid": "^9.0.2",
285
288
  "@typescript-eslint/eslint-plugin": "^7.7.0",
286
289
  "@typescript-eslint/parser": "^7.7.0",
@@ -312,7 +315,6 @@
312
315
  "eslint-plugin-prettier": "^5.1.3",
313
316
  "eslint-plugin-react": "^7.34.1",
314
317
  "eslint-plugin-react-hooks": "^4.6.0",
315
- "full-icu": "1.4.0",
316
318
  "html-webpack-plugin": "5.5.0",
317
319
  "identity-obj-proxy": "3.0.0",
318
320
  "jest": "26.6.3",
@@ -363,8 +365,7 @@
363
365
  "webpack-bundle-analyzer": "4.10.1",
364
366
  "webpack-dev-server": "4.11.1",
365
367
  "webpack-node-externals": "3.0.0",
366
- "why": "0.6.2",
367
- "@plone/types": "2.0.0-alpha.6",
368
+ "@plone/types": "2.0.0-alpha.7",
368
369
  "@plone/volto-coresandbox": "1.0.0"
369
370
  },
370
371
  "volta": {
package/razzle.config.js CHANGED
@@ -414,6 +414,22 @@ const defaultModify = ({
414
414
  ]
415
415
  : [];
416
416
 
417
+ // If Volto is served under a subpath,
418
+ // we have to adjust where Webpack assets are served too.
419
+ const subpathPrefix = process.env.RAZZLE_SUBPATH_PREFIX || '';
420
+ if (subpathPrefix) {
421
+ if (target === 'web' && dev) {
422
+ if (config.devServer.devMiddleware) {
423
+ config.devServer.devMiddleware.publicPath = subpathPrefix;
424
+ } else {
425
+ config.devServer.publicPath += `${subpathPrefix.slice(1)}/`;
426
+ }
427
+ }
428
+ const publicPath = config.output.publicPath;
429
+ if (publicPath.indexOf(subpathPrefix) === -1) {
430
+ config.output.publicPath = `${publicPath}${subpathPrefix.slice(1)}/`;
431
+ }
432
+ }
417
433
  return config;
418
434
  };
419
435
 
@@ -1,10 +1,12 @@
1
- import React from 'react';
2
1
  import PropTypes from 'prop-types';
3
2
  import loadable from '@loadable/component';
4
3
  import 'react-image-gallery/styles/css/image-gallery.css';
5
4
  import { Button } from 'semantic-ui-react';
6
5
  import Icon from '@plone/volto/components/theme/Icon/Icon';
7
- import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
6
+ import {
7
+ addSubpathPrefix,
8
+ flattenToAppURL,
9
+ } from '@plone/volto/helpers/Url/Url';
8
10
  import config from '@plone/volto/registry';
9
11
 
10
12
  import galleryLeftSVG from '@plone/volto/icons/left-key.svg';
@@ -81,10 +83,10 @@ const ImageGalleryTemplate = ({ items }) => {
81
83
  );
82
84
  const imagesInfo = renderItems.map((item) => {
83
85
  return {
84
- original: `${flattenToAppURL(item['@id'])}/@@images/${
86
+ original: `${addSubpathPrefix(flattenToAppURL(item['@id']))}/@@images/${
85
87
  item.image_field
86
88
  }/large`,
87
- thumbnail: `${flattenToAppURL(item['@id'])}/@@images/${
89
+ thumbnail: `${addSubpathPrefix(flattenToAppURL(item['@id']))}/@@images/${
88
90
  item.image_field
89
91
  }/thumb`,
90
92
  };
@@ -44,11 +44,11 @@ const ContentsBreadcrumbs = (props) => {
44
44
  </Link>
45
45
  <Breadcrumb.Divider />
46
46
  <Link
47
- to={`${navroot['@id']}/contents`}
47
+ to={`${navroot?.['@id']}/contents`}
48
48
  className="section"
49
- title={navroot.title}
49
+ title={navroot?.title}
50
50
  >
51
- {navroot.title}
51
+ {navroot?.title}
52
52
  </Link>
53
53
  </>
54
54
  )}
@@ -1,6 +1,10 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import cx from 'classnames';
3
- import { flattenToAppURL, flattenScales } from '@plone/volto/helpers/Url/Url';
3
+ import {
4
+ flattenToAppURL,
5
+ flattenScales,
6
+ addSubpathPrefix,
7
+ } from '@plone/volto/helpers/Url/Url';
4
8
 
5
9
  /**
6
10
  * Image component
@@ -30,7 +34,7 @@ export default function Image({
30
34
  attrs.className = cx(className, { responsive }) || undefined;
31
35
 
32
36
  if (!item && src) {
33
- attrs.src = src;
37
+ attrs.src = addSubpathPrefix(src);
34
38
  } else {
35
39
  const isFromRealObject = !item.image_scales;
36
40
  const imageFieldWithDefault = imageField || item.image_field || 'image';
@@ -46,9 +50,11 @@ export default function Image({
46
50
 
47
51
  const isSvg = image['content-type'] === 'image/svg+xml';
48
52
  // In case `base_path` is present (`preview_image_link`) use it as base path
49
- const basePath = image.base_path || item['@id'];
53
+ const basePath = addSubpathPrefix(
54
+ flattenToAppURL(image.base_path || item['@id']),
55
+ );
56
+ attrs.src = `${basePath}/${image.download}`;
50
57
 
51
- attrs.src = `${flattenToAppURL(basePath)}/${image.download}`;
52
58
  attrs.width = image.width;
53
59
  attrs.height = image.height;
54
60
 
@@ -67,10 +73,7 @@ export default function Image({
67
73
  });
68
74
 
69
75
  attrs.srcSet = sortedScales
70
- .map(
71
- (scale) =>
72
- `${flattenToAppURL(basePath)}/${scale.download} ${scale.width}w`,
73
- )
76
+ .map((scale) => `${basePath}/${scale.download} ${scale.width}w`)
74
77
  .join(', ');
75
78
  }
76
79
  }
@@ -0,0 +1,89 @@
1
+ import { Link } from 'react-router-dom';
2
+
3
+ import { useSelector } from 'react-redux';
4
+ import cx from 'classnames';
5
+
6
+ import Helmet from '@plone/volto/helpers/Helmet/Helmet';
7
+ import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
8
+ import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
9
+ import { toReactIntlLang } from '@plone/volto/helpers/Utils/Utils';
10
+
11
+ import { defineMessages, useIntl, type IntlShape } from 'react-intl';
12
+ import type {
13
+ Content,
14
+ GetSiteResponse,
15
+ GetTranslationResponse,
16
+ } from '@plone/types';
17
+
18
+ const messages = defineMessages({
19
+ switchLanguageTo: {
20
+ id: 'Switch to',
21
+ defaultMessage: 'Switch to',
22
+ },
23
+ });
24
+
25
+ type FormState = {
26
+ content: {
27
+ data: Content;
28
+ };
29
+ intl: IntlShape;
30
+ site: {
31
+ data: GetSiteResponse;
32
+ };
33
+ };
34
+
35
+ const LanguageSelector = ({
36
+ onClickAction = () => {},
37
+ }: {
38
+ onClickAction?: () => void;
39
+ }) => {
40
+ const intl = useIntl();
41
+ const currentLang = useSelector<FormState, IntlShape['locale']>(
42
+ (state) => state.intl.locale,
43
+ );
44
+ const translations = useSelector<
45
+ FormState,
46
+ GetTranslationResponse['items'] | undefined
47
+ >((state) => state.content.data?.['@components']?.translations?.items);
48
+ const isMultilingual = useSelector<
49
+ FormState,
50
+ GetSiteResponse['features']['multilingual']
51
+ >((state) => state.site.data.features?.multilingual);
52
+ const availableLanguages = useSelector<
53
+ FormState,
54
+ GetSiteResponse['plone.available_languages']
55
+ >((state) => state.site.data?.['plone.available_languages']);
56
+
57
+ return isMultilingual ? (
58
+ <div className="language-selector">
59
+ {availableLanguages?.map((lang) => {
60
+ const langKey = lang as keyof typeof langmap;
61
+ const translation = translations?.find(
62
+ (t: { language: string }) => t.language === lang,
63
+ );
64
+ return (
65
+ <Link
66
+ aria-label={`${intl.formatMessage(
67
+ messages.switchLanguageTo,
68
+ )} ${langmap[langKey].nativeName.toLowerCase()}`}
69
+ className={cx({ selected: toReactIntlLang(lang) === currentLang })}
70
+ to={translation ? flattenToAppURL(translation['@id']) : `/${lang}`}
71
+ title={langmap[langKey].nativeName}
72
+ onClick={() => {
73
+ onClickAction();
74
+ }}
75
+ key={`language-selector-${lang}`}
76
+ >
77
+ {langmap[langKey].nativeName}
78
+ </Link>
79
+ );
80
+ })}
81
+ </div>
82
+ ) : (
83
+ <Helmet>
84
+ <html lang={toReactIntlLang(currentLang)} />
85
+ </Helmet>
86
+ );
87
+ };
88
+
89
+ export default LanguageSelector;
@@ -29,7 +29,7 @@ const RequestTimeout = () => (
29
29
  <h1 style={{ textAlign: 'center', lineHeight: '40px' }}>
30
30
  <FormattedMessage
31
31
  id="No connection to the server"
32
- defaultMessage="There is no connection to the server, due to a timeout o no network connection."
32
+ defaultMessage="There is no connection to the server, due to a timeout or no network connection."
33
33
  />
34
34
  <br />
35
35
  <a href={config.settings.apiPath}>{config.settings.apiPath}</a>
@@ -30,10 +30,6 @@ import languages from '@plone/volto/constants/Languages.cjs';
30
30
  const host = process.env.HOST || 'localhost';
31
31
  const port = process.env.PORT || '3000';
32
32
 
33
- const apiPath =
34
- process.env.RAZZLE_API_PATH ||
35
- (__DEVELOPMENT__ ? `http://${host}:${port}` : '');
36
-
37
33
  const getServerURL = (url) => {
38
34
  if (!url) return;
39
35
  const apiPathURL = parseUrl(url);
@@ -46,12 +42,14 @@ const getServerURL = (url) => {
46
42
  // if RAZZLE_PUBLIC_URL is present, use it
47
43
  // if in DEV, use the host/port combination by default
48
44
  // if in PROD, assume it's RAZZLE_API_PATH server name (no /api or alikes) or fallback
49
- // to DEV settings if RAZZLE_API_PATH is not present
45
+ // to DEV settings if RAZZLE_API_PATH is not present.
46
+ // Finally, add the subpath, if there is one.
50
47
  const publicURL =
51
- process.env.RAZZLE_PUBLIC_URL ||
52
- (__DEVELOPMENT__
53
- ? `http://${host}:${port}`
54
- : getServerURL(process.env.RAZZLE_API_PATH) || `http://${host}:${port}`);
48
+ (process.env.RAZZLE_PUBLIC_URL ||
49
+ (__DEVELOPMENT__
50
+ ? `http://${host}:${port}`
51
+ : getServerURL(process.env.RAZZLE_API_PATH) ||
52
+ `http://${host}:${port}`)) + (process.env.RAZZLE_SUBPATH_PREFIX || '');
55
53
 
56
54
  const serverConfig =
57
55
  typeof __SERVER__ !== 'undefined' && __SERVER__
@@ -65,7 +63,10 @@ let config = {
65
63
  // The URL Volto is going to be served (see sensible defaults above)
66
64
  publicURL,
67
65
  okRoute: '/ok',
68
- apiPath,
66
+ // Base URL for API requests from the browser.
67
+ // If not explicitly set, use the publicURL (seamless mode) --
68
+ // but in that case it will be updated in server.jsx for each request.
69
+ apiPath: process.env.RAZZLE_API_PATH || publicURL,
69
70
  apiExpanders: [
70
71
  // Added here for documentation purposes, added at the end because it
71
72
  // depends on a value of this object.
@@ -99,6 +100,7 @@ let config = {
99
100
  // apiPath: process.env.RAZZLE_API_PATH || 'http://localhost:8081/db/web', // for guillotina
100
101
  actions_raising_api_errors: ['GET_CONTENT', 'UPDATE_CONTENT'],
101
102
  internalApiPath: process.env.RAZZLE_INTERNAL_API_PATH || undefined,
103
+ subpathPrefix: process.env.RAZZLE_SUBPATH_PREFIX || '',
102
104
  websockets: process.env.RAZZLE_WEBSOCKETS || false,
103
105
  // TODO: legacyTraverse to be removed when the use of the legacy traverse is deprecated.
104
106
  legacyTraverse: process.env.RAZZLE_LEGACY_TRAVERSE || false,
@@ -1,7 +1,6 @@
1
1
  import imagesMiddleware from '@plone/volto/express-middleware/images';
2
2
  import filesMiddleware from '@plone/volto/express-middleware/files';
3
3
  import robotstxtMiddleware from '@plone/volto/express-middleware/robotstxt';
4
- import okMiddleware from '@plone/volto/express-middleware/ok';
5
4
  import sitemapMiddleware from '@plone/volto/express-middleware/sitemap';
6
5
  import staticsMiddleware from '@plone/volto/express-middleware/static';
7
6
  import devProxyMiddleware from '@plone/volto/express-middleware/devproxy';
@@ -12,7 +11,6 @@ const settings = {
12
11
  filesMiddleware(),
13
12
  imagesMiddleware(),
14
13
  robotstxtMiddleware(),
15
- okMiddleware(),
16
14
  sitemapMiddleware(),
17
15
  staticsMiddleware(),
18
16
  ],
@@ -13,7 +13,10 @@ const filter = function (pathname, req) {
13
13
  // Check if pathname is defined, there are some corner cases that pathname is null
14
14
  if (pathname) {
15
15
  // This is the proxy to the API in case the accept header is 'application/json'
16
- return config.settings.devProxyToApiPath && pathname.startsWith('/++api++');
16
+ return (
17
+ config.settings.devProxyToApiPath &&
18
+ pathname.startsWith(`${config.settings.subpathPrefix}/++api++`)
19
+ );
17
20
  } else {
18
21
  return false;
19
22
  }
@@ -78,17 +81,24 @@ export default function devProxyMiddleware() {
78
81
  },
79
82
  pathRewrite: (path, req) => {
80
83
  const { apiPathURL, instancePath } = getEnv();
84
+ const vhSubpath = config.settings.subpathPrefix
85
+ ? config.settings.subpathPrefix
86
+ .split('/')
87
+ .filter(Boolean)
88
+ .map((part) => '/_vh_' + part)
89
+ .join('')
90
+ : '';
81
91
  const target =
82
92
  config.settings.proxyRewriteTarget ||
83
93
  `/VirtualHostBase/${apiPathURL.protocol.slice(0, -1)}/${
84
94
  apiPathURL.hostname
85
- }:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot`;
95
+ }:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot${vhSubpath}`;
86
96
 
87
- return `${target}${path.replace('/++api++', '')}`;
97
+ return `${target}${path.replace(`${config.settings.subpathPrefix}/++api++`, '')}`;
88
98
  },
99
+ changeOrigin: true,
89
100
  logLevel: process.env.DEBUG_HPM ? 'debug' : 'silent',
90
101
  ...(process.env.RAZZLE_DEV_PROXY_INSECURE && {
91
- changeOrigin: true,
92
102
  secure: false,
93
103
  }),
94
104
  });