From 6a08fb309fe7648ef3411a53d440ee25c8b01d93 Mon Sep 17 00:00:00 2001
From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com>
Date: Thu, 10 Sep 2026 16:25:47 +0100
Subject: [PATCH] Load projects in the correct locale
Some project identifiers are shared between locales, this is the case for the Experience CS preview page. We need to send the locale with the request to load the correct project.
Note that we could alternatively set cookies and send them with the request (this is what ExCS does).
This doesn't apply to saving yet as editable projects only exist in one locale for now.
---
apps/scratch-frame/src/ScratchEditor.jsx | 5 ++++
apps/scratch-frame/src/ScratchEditor.test.jsx | 26 +++++++++++++++++++
apps/scratch-frame/src/scratch.jsx | 1 +
apps/scratch-frame/src/scratch.test.js | 4 ++-
cypress/e2e/spec-scratch.cy.js | 14 +++++++++-
5 files changed, 48 insertions(+), 2 deletions(-)
diff --git a/apps/scratch-frame/src/ScratchEditor.jsx b/apps/scratch-frame/src/ScratchEditor.jsx
index 4c159827c..2a120e52a 100644
--- a/apps/scratch-frame/src/ScratchEditor.jsx
+++ b/apps/scratch-frame/src/ScratchEditor.jsx
@@ -40,6 +40,7 @@ const ScratchEditor = ({
locale,
apiUrl,
accessToken: initialAccessToken,
+ projectLocale,
}) => {
const [accessToken, setAccessToken] = useState(initialAccessToken);
@@ -89,6 +90,10 @@ const ScratchEditor = ({
basePath={`${import.meta.env.REACT_APP_SCRATCH_FRAME_URL}/scratch-gui/`}
onStorageInit={(storage) => {
scratchFetchApiRef.current = storage.scratchFetch;
+ scratchFetchApiRef.current.setMetadata(
+ "X-Project-Locale",
+ projectLocale,
+ );
if (accessToken) {
scratchFetchApiRef.current.setMetadata("Authorization", accessToken);
}
diff --git a/apps/scratch-frame/src/ScratchEditor.test.jsx b/apps/scratch-frame/src/ScratchEditor.test.jsx
index 6b2483fd0..185e7a1ed 100644
--- a/apps/scratch-frame/src/ScratchEditor.test.jsx
+++ b/apps/scratch-frame/src/ScratchEditor.test.jsx
@@ -102,6 +102,32 @@ describe("ScratchEditor", () => {
);
});
+ test("sends the project locale with scratch requests after storage init", () => {
+ render(
+ ,
+ );
+
+ const scratchGuiProps = mockWrappedScratchGui.mock.calls[0][0];
+ const scratchStorage = {
+ scratchFetch: {
+ setMetadata: vi.fn(),
+ },
+ };
+
+ scratchGuiProps.onStorageInit(scratchStorage);
+
+ expect(scratchStorage.scratchFetch.setMetadata).toHaveBeenCalledWith(
+ "X-Project-Locale",
+ "fr-FR",
+ );
+ });
+
test("updates scratchFetch metadata when scratch-gui-update-token message is received", () => {
render(
>,
);
diff --git a/apps/scratch-frame/src/scratch.test.js b/apps/scratch-frame/src/scratch.test.js
index bd325320a..21f8f34a3 100644
--- a/apps/scratch-frame/src/scratch.test.js
+++ b/apps/scratch-frame/src/scratch.test.js
@@ -149,7 +149,9 @@ describe("scratch handshake retries", () => {
);
await loadScratchModule();
- expect(mountScratchEditor().locale).toBe(expected);
+ const scratchEditorProps = mountScratchEditor();
+ expect(scratchEditorProps.locale).toBe(expected);
+ expect(scratchEditorProps.projectLocale).toBe(locale);
},
);
diff --git a/cypress/e2e/spec-scratch.cy.js b/cypress/e2e/spec-scratch.cy.js
index 8c4c14f90..4d16536e7 100644
--- a/cypress/e2e/spec-scratch.cy.js
+++ b/cypress/e2e/spec-scratch.cy.js
@@ -10,6 +10,7 @@ import {
const origin = "http://localhost:3011/web-component.html";
const scratchFrameOrigin = Cypress.env("REACT_APP_SCRATCH_FRAME_URL");
const authKey = "oidc.user:https://auth-v1.raspberrypi.org:editor-api";
+const scratchProjectsApiMatcher = "**/api/scratch/projects/**";
const user = {
access_token: "dummy-access-token",
profile: {
@@ -107,6 +108,18 @@ describe("Scratch locale", () => {
});
});
+ it("sets a header with the project locale", () => {
+ cy.intercept("GET", scratchProjectsApiMatcher).as("scratchProjectRequest");
+
+ cy.visit(scratchProjectURL({ locale: "fr-FR" }));
+
+ cy.wait("@scratchProjectRequest")
+ .its("request.headers")
+ .then((headers) => {
+ expect(headers["x-project-locale"]).to.equal("fr-FR");
+ });
+ });
+
it("falls back to English when Scratch does not support the locale", () => {
cy.visit(scratchProjectURL({ locale: "vls-BE" }));
@@ -220,7 +233,6 @@ describe("Scratch save integration", () => {
});
describe("Scratch Authorization header", () => {
- const scratchProjectsApiMatcher = "**/api/scratch/projects/**";
const remixApiMatcher = "**/api/projects/*/remix";
it("includes Authorization header when authKey and access token are present in localStorage", () => {