From f18d9998879c236834dac11225350e0174d937f9 Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:05:21 +0100 Subject: [PATCH] Correctly load the localized Scratch project In most places in editor we only have one locale per project. One place this isn't true is the new Preview page for Experience CS scratch projects- there are multiple projects that share one identifier with translated content. Elsewhere we are loading projects we use ProjectLoader, make sure we use it here to load Scratch projects too. Since we have limited ways that we can alter Scratch, we can't easily pass in a param with the locale. Instead use a header. An alternative would be to use a cookie (this is what ExCS currently does). I received an n plus one warning for loading Scratch Components - putting an include in here causes bullet to warn elsewhere as we don't always need to load the Scratch component. Since this is just a 1+1 query, I think this is fine to ignore. --- app/controllers/api/scratch/projects_controller.rb | 7 ++++++- config/initializers/bullet.rb | 5 ++++- .../scratch/showing_a_scratch_project_spec.rb | 13 +++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/scratch/projects_controller.rb b/app/controllers/api/scratch/projects_controller.rb index 54d549752..5bb84f26e 100644 --- a/app/controllers/api/scratch/projects_controller.rb +++ b/app/controllers/api/scratch/projects_controller.rb @@ -105,7 +105,12 @@ def move_pending_scratch_upload_to_remix(pending_upload, remix_project) end def load_project - @project = Project.find_by!(identifier: params.expect(:id), project_type: Project::Types::CODE_EDITOR_SCRATCH) + @project = ProjectLoader.new(params[:id], project_locale).load + raise ActiveRecord::RecordNotFound unless @project&.scratch_project? + end + + def project_locale + request.headers['X-Project-Locale'] end end end diff --git a/config/initializers/bullet.rb b/config/initializers/bullet.rb index b40b53940..3eb731b7f 100644 --- a/config/initializers/bullet.rb +++ b/config/initializers/bullet.rb @@ -1,3 +1,6 @@ # frozen_string_literal: true -Bullet.add_safelist type: :unused_eager_loading, class_name: 'Project', association: :images_attachments if Rails.env.development? || Rails.env.test? +if Rails.env.development? || Rails.env.test? + Bullet.add_safelist type: :unused_eager_loading, class_name: 'Project', association: :images_attachments + Bullet.add_safelist type: :n_plus_one_query, class_name: 'Project', association: :scratch_component +end diff --git a/spec/features/scratch/showing_a_scratch_project_spec.rb b/spec/features/scratch/showing_a_scratch_project_spec.rb index 29524cf46..9d7db76c7 100644 --- a/spec/features/scratch/showing_a_scratch_project_spec.rb +++ b/spec/features/scratch/showing_a_scratch_project_spec.rb @@ -57,6 +57,19 @@ expect(response.parsed_body.fetch('targets').pluck('name')).to eq(%w[Stage Sprite1 Sprite2]) end + it 'prefers the project matching the project locale header' do + identifier = 'locale-specific-project' + english_project = create(:project, project_type: Project::Types::CODE_EDITOR_SCRATCH, identifier:, locale: 'en', user_id: nil) + create(:scratch_component, project: english_project, content: { targets: [{ name: 'English Stage', isStage: true }] }) + french_project = create(:project, project_type: Project::Types::CODE_EDITOR_SCRATCH, identifier:, locale: 'fr-FR', user_id: nil) + create(:scratch_component, project: french_project, content: { targets: [{ name: 'French Stage', isStage: true }] }) + + get "/api/scratch/projects/#{identifier}", headers: { 'X-Project-Locale' => 'fr-FR' } + + expect(response).to have_http_status(:ok) + expect(response.parsed_body.fetch('targets').pluck('name')).to eq(['French Stage']) + end + it 'returns a 404 if project does not exist' do authenticated_in_hydra_as(teacher) get '/api/scratch/projects/non_existent_project', headers: headers