Skip to content

Fix translating taxonomies items doesn't work#16122

Merged
andreslucena merged 13 commits intodecidim:developfrom
i-need-another-coffee:fix/taxonomies-trasnlations
Feb 19, 2026
Merged

Fix translating taxonomies items doesn't work#16122
andreslucena merged 13 commits intodecidim:developfrom
i-need-another-coffee:fix/taxonomies-trasnlations

Conversation

@alecslupu
Copy link
Copy Markdown
Contributor

@alecslupu alecslupu commented Feb 16, 2026

🎩 What? Why?

In this PR i am fixing the taxonomies that cannot be translated. While investigating, i have found that the language selector was not registering the tab change event, therefore everytime you'd select a new language, only default was being changed.

📌 Related Issues

Link your PR to an issue

Testing

  1. Login as admin visit admin / settings/ taxonomies
  2. Click on an existing taxonomy
  3. Identify a taxonomy item and choose to edit
  4. Once the drawer is opened, choose to inspect your elements
  5. See that when you change your language, nothing changes with regard to field visibility
  6. Apply patch, restart the application (to reload the new JS)
  7. Refresh, and repeat 2,3,4
  8. See that when you change the language, the visibility is being changed
  9. Click update item
  10. See the values persisted, and in drawer you are able to change the fields

📷 Screenshots

Please add screenshots of the changes you are proposing
Description

♥️ Thank you!

Summary by CodeRabbit

  • Refactor

    • Modernized language selector: replaced one-time initializer with per-element handling and introduced a Stimulus language-change controller with data-controller bindings.
  • UX / Accessibility

    • Taxonomy actions aria-label now uses translated_attribute for translated resource labels.
  • Tests

    • Added unit tests for the language-change controller and expanded system tests covering taxonomy translations across multiple locales.

@alecslupu alecslupu added the release: v0.31 Issues or PRs that need to be tackled for v0.31 label Feb 16, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Replaces the old initLanguageChangeSelect initializer with a Stimulus language-change controller, updates form builder to bind the controller, removes the legacy initializer, adjusts admin entrypoints to deprecate per-element, and adds unit and system test updates for multi-language taxonomy flows.

Changes

Cohort / File(s) Summary
Stimulus controller & tests
decidim-core/app/packs/src/decidim/controllers/language_change/controller.js, decidim-core/app/packs/src/decidim/controllers/language_change/language_change.test.js
Adds a Stimulus language-change controller that listens for select changes and toggles tab panels (aria-hidden / is-active). Adds unit tests for lifecycle, change handling, and edge cases.
Entrypoints & initialization
decidim-admin/app/packs/entrypoints/decidim_admin.js, decidim-forms/app/packs/src/decidim/forms/admin/forms.js
Removes direct initializer imports/calls and replaces them with per-element deprecation calls: iterate select.language-change and call window.deprecate(..., "language-change", "select.language-change").
Form builder binding
decidim-core/lib/decidim/form_builder.rb
Adds data: { controller: "language-change" } to the language selector <select> so Stimulus binds automatically.
Legacy initializer removed
decidim-admin/app/packs/src/decidim/admin/choose_language.js
Removes the exported initLanguageChangeSelect(elements) function previously responsible for wiring language select change behavior.
Template & system tests
decidim-admin/app/views/decidim/admin/taxonomies/_taxonomy_actions.html.erb, decidim-admin/spec/system/admin_manages_taxonomies_spec.rb
ARIA label uses translated_attribute(taxonomy.name); system spec adds locale setup/teardown and a multi-language test that creates/edits taxonomy translations and verifies per-locale persistence.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Select as "select.language-change"
  participant Controller as "Stimulus language-change"
  participant Tabs as "Tabs container (tab-content)"

  User->>Select: choose language option
  Select->>Controller: change event
  Controller->>Tabs: find current active pane
  Controller->>Tabs: set current pane aria-hidden=true, remove is-active
  Controller->>Tabs: activate target pane aria-hidden=false, add is-active
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested labels

module: dev, type: internal

Suggested reviewers

  • andreslucena

Poem

🐰 From old functions thrown to the bin,
A tiny controller hops right in,
Tabs wake and sleep with gentle cheer,
Translations find each language clear,
I nibble carrots and clap — hooray, it's here! 🥕✨

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix translating taxonomies items doesn't work' clearly describes the main change—fixing a taxonomy translation bug where language switching wasn't updating form field visibility.
Linked Issues check ✅ Passed The PR directly addresses issue #15717 by refactoring language selection handling: it introduces a Stimulus controller for language-change events, removes the broken direct initializer, updates form binding, and adds comprehensive tests verifying language tab switching works correctly.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the taxonomy translation bug: new Stimulus controller for language switching, updated form builder bindings, removed broken initializer, and added tests verifying the fix. No unrelated refactoring or feature additions detected.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@alecslupu alecslupu marked this pull request as ready for review February 16, 2026 10:34
Copilot AI review requested due to automatic review settings February 16, 2026 10:34
github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@decidim-admin/app/packs/entrypoints/decidim_admin.js`:
- Around line 29-30: The forEach callback currently uses an implicit-return
arrow which triggers the lint rule; change the callback to use an explicit block
body that calls window.deprecate without returning its value. Locate the query
selector expression
document.querySelectorAll("select.language-change").forEach(...) and replace the
arrow callback with a block-style callback that invokes
window.deprecate(container, "language-change", "select.language-change") inside
the block (no return), ensuring the lint error is resolved.

In
`@decidim-admin/app/packs/src/decidim/admin/controllers/language-change/controller.js`:
- Around line 13-21: In handleChange, the DOM traversal
(event.target.parentElement.parentElement.nextElementSibling) and unchecked
querySelector(".is-active") calls can return null and cause runtime errors; fix
by locating the tabs container via a stable selector (e.g., a data attribute or
a Stimulus target) instead of fragile parent/next traversal, then guard all DOM
lookups with null checks (ensure tabsContent exists, assign result of
tabsContent.querySelector(".is-active") to a variable and check it before
accessing ariaHidden/classList, and likewise check the targetTabPane element
before toggling). Update the handler to use these safer lookups and
short-circuit if required elements are missing.

In `@decidim-forms/app/packs/src/decidim/forms/admin/forms.js`:
- Around line 392-393: The forEach callback on
document.querySelectorAll("select.language-change") currently uses a concise
arrow that implicitly returns window.deprecate(...); change the callback to a
block body (wrap in { ... }) and call window.deprecate(container,
"language-change", "select.language-change") inside the braces (no returned
value) so the forEach callback does not implicitly return; this affects the
forEach iterator where window.deprecate is invoked for each container.
🧹 Nitpick comments (3)
decidim-admin/app/packs/src/decidim/admin/controllers/language-change/controller.js (1)

3-11: Consider using Stimulus conventions instead of manual event listener management.

Stimulus provides a declarative data-action attribute for binding events and lifecycle-managed method binding. Manual addEventListener/removeEventListener + .bind(this) is unnecessary boilerplate when the framework handles it. For example, the form builder could emit data-action="change->language-change#handleChange" on the <select>, and you could remove the connect/disconnect hooks entirely.

decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (2)

192-192: Avoid sleep(2) in system specs — use Capybara's waiting assertions instead.

Hard sleeps make tests slow and flaky. Capybara's matchers already retry/wait. Consider waiting on a visible UI change (e.g., a success flash message or the updated content) before asserting on the database:

expect(page).to have_content("My english value") # wait for UI update
expect(taxonomy.reload.children.last.name["en"]).to eq("My english value")

The same applies to line 206.


143-145: Redundant let(:organization) — identical to the outer definition on line 6.

Line 144 redefines organization with the same factory call as line 6. If no customization is intended, this can be removed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes multilingual taxonomy item editing by ensuring the language selector properly switches the active translation pane (especially for dynamically loaded/drawer content), preventing secondary-language edits from overwriting the primary language.

Changes:

  • Replaces the legacy select.language-change event binding with a Stimulus language-change controller.
  • Updates the shared FormBuilder language selector markup to emit data-controller="language-change".
  • Adds a system spec covering independent persistence of translations when an organization has >4 locales.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
decidim-forms/app/packs/src/decidim/forms/admin/forms.js Removes legacy initializer call; adds deprecation checks after dynamic field insertion.
decidim-core/lib/decidim/form_builder.rb Adds data-controller="language-change" to the language selector <select>.
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb Adds coverage for creating/updating taxonomy item translations in multiple locales.
decidim-admin/app/views/decidim/admin/taxonomies/_taxonomy_actions.html.erb Fixes aria-label to use translated_attribute for taxonomy names.
decidim-admin/app/packs/src/decidim/admin/controllers/language-change/controller.js New Stimulus controller that toggles the active translation pane on <select> change.
decidim-admin/app/packs/src/decidim/admin/choose_language.js / decidim-admin/app/packs/entrypoints/decidim_admin.js Removes old JS and replaces initialization with deprecation warnings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (3)

157-164: Hardcoded locale reset in after may drift from actual defaults.

The after block resets locales to %w(en ca es), but if the test suite's default locales ever change, this will silently diverge. Consider capturing the original values in before and restoring them in after.

♻️ Suggested approach
+      let(:original_i18n_locales) { I18n.available_locales }
+      let(:original_decidim_locales) { Decidim.available_locales }
+
       before do
+        # originals are captured by the lets above before mutation
         I18n.available_locales = available_locales
         ...
       end
 
       after do
-        I18n.available_locales = %w(en ca es)
-        Decidim.available_locales = %w(en ca es)
+        I18n.available_locales = original_i18n_locales
+        Decidim.available_locales = original_decidim_locales
         I18n.backend.reload!
         ...
       end

191-193: Replace sleep(2) with Capybara waiting assertions.

sleep is a common source of flaky tests in CI — too short on slow runners, too long everywhere else. Since the create/update actions likely produce a flash message or redirect, wait on a visible indicator instead, then assert on the DB.

-        sleep(2)
+        expect(page).to have_content("Item successfully created") # or whatever flash text is shown
         expect(taxonomy.reload.children.last.name["en"]).to eq("My english value")
-        sleep(2)
+        expect(page).to have_content("Item successfully updated") # or whatever flash text is shown
         expect(taxonomy.reload.children.last.name["en"]).to eq("My modified english value")

Also applies to: 205-207


174-182: first('input[type="text"]', visible: true) could be fragile if tab switch hasn't completed.

Consider adding a brief assertion that the expected tab panel is active before interacting with the input, or use a more specific selector targeting the locale panel (e.g., find('[id*="panel"]', visible: true) input). This guards against race conditions where the previous panel's input is still visible.

github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (2)

146-164: remove_const + load is fragile; consider extracting a shared helper.

The send(:remove_const, :TaxonomyForm) / load dance appears both in the before and after blocks. If more specs need multi-locale form testing, this boilerplate will be duplicated. A small shared helper (e.g., reload_form_class(Decidim::Admin, :TaxonomyForm)) would reduce duplication and keep the teardown in sync with setup.

Also, the after block hardcodes %w(en ca es) — if the default test locales ever change, this will silently produce the wrong reset. Consider capturing the original values before overriding:

Sketch
# inside the context block
let(:original_i18n_locales) { I18n.available_locales }
let(:original_decidim_locales) { Decidim.available_locales }

after do
  I18n.available_locales = original_i18n_locales
  Decidim.available_locales = original_decidim_locales
  # ...
end

174-182: first('input[type="text"]', visible: true) is fragile — scope to the active tab panel.

If the form ever gains another visible text input (e.g., a code or slug field), this selector will silently fill the wrong field without failing. A safer approach is to scope within the active tab panel:

Example
-          first('input[type="text"]', visible: true).set("My english value")
+          find(".tabs-content [aria-hidden='false'] input[type='text']").set("My english value")

(Adjust the panel selector to match the actual DOM structure produced by translated / the Stimulus controller.)

The same applies to lines 179, 197, and 200.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 16, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 16, 2026
github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (1)

6-22: ⚠️ Potential issue | 🔴 Critical

available_locales is undefined for most test contexts — all tests outside the nested context will error.

available_locales is referenced in the top-level before block (lines 11, 12, 18) but is only defined via let at line 162 inside context "and the organization has multiple languages". Every other test context will raise a NameError (or RSpec's "method undefined" equivalent) when the before block runs.

Add a top-level let with the default locales (matching the hardcoded restore in after):

Proposed fix
   let(:organization) { create(:organization) }
   let(:user) { create(:user, :admin, :confirmed, organization:) }
   let(:attributes) { attributes_for(:taxonomy) }
+  let(:available_locales) { %w(en ca es) }
 
   before do
     I18n.available_locales = available_locales
🧹 Nitpick comments (2)
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (2)

25-32: Consider saving and restoring original locale values instead of hardcoding.

The after block hardcodes %w(en ca es) as the restored defaults. If the project's default test locales ever change, this will silently drift. Capturing the originals before mutation is more resilient:

Sketch
# at the top of the describe block:
let(:original_i18n_locales) { I18n.available_locales }
let(:original_decidim_locales) { Decidim.available_locales }

after do
  I18n.available_locales = original_i18n_locales
  Decidim.available_locales = original_decidim_locales
  # ...
end

Note: let is lazily evaluated, so these would be captured on first access — you'd need to force evaluation before the before block mutates them (e.g., with let! or by referencing them in before before the mutation).


172-180: The first('input[type="text"]', visible: true) selector is fragile.

If the form ever gains additional visible text inputs (e.g., a slug or code field), this will silently target the wrong element. Consider scoping to the active tab panel or using a more specific selector tied to the locale field name, e.g.:

find('input[name*="[name_en]"]')
# or scope within the visible panel:
within(".tabs-panel.is-active") { fill_in ... }

This is fine for now given the current form structure.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 16, 2026
github-actions[bot]
github-actions bot previously approved these changes Feb 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
decidim-admin/spec/system/admin_manages_taxonomies_spec.rb (1)

145-166: Restore original locales instead of hard-coding defaults.

Hard-coding %w(en ca es) in the teardown couples this spec to a specific global default and can leak into later specs if defaults change. Capture and restore the previous values for better test isolation.

♻️ Suggested change
       before do
+        `@original_i18n_locales` = I18n.available_locales
+        `@original_decidim_locales` = Decidim.available_locales
         I18n.available_locales = %w(en ca es fr it)
         Decidim.available_locales = %w(en ca es fr it)
         I18n.backend.reload!
@@
       after do
-        I18n.available_locales = %w(en ca es)
-        Decidim.available_locales = %w(en ca es)
+        I18n.available_locales = `@original_i18n_locales`
+        Decidim.available_locales = `@original_decidim_locales`
         I18n.backend.reload!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@decidim-admin/spec/system/admin_manages_taxonomies_spec.rb` around lines 145
- 166, Store the current I18n.available_locales and Decidim.available_locales
into local variables at the start of the spec (before you change them), then in
the after block restore those saved values and call I18n.backend.reload!; update
the teardown to use the saved variables instead of the hard-coded %w(en ca es)
so I18n.available_locales and Decidim.available_locales are returned to their
original states (keep the existing remove_const/load and organization.update!
handling unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@decidim-admin/spec/system/admin_manages_taxonomies_spec.rb`:
- Around line 145-166: Store the current I18n.available_locales and
Decidim.available_locales into local variables at the start of the spec (before
you change them), then in the after block restore those saved values and call
I18n.backend.reload!; update the teardown to use the saved variables instead of
the hard-coded %w(en ca es) so I18n.available_locales and
Decidim.available_locales are returned to their original states (keep the
existing remove_const/load and organization.update! handling unchanged).

@froger
Copy link
Copy Markdown
Contributor

froger commented Feb 19, 2026

Tested and working locally, thanks <3

Copy link
Copy Markdown
Member

@andreslucena andreslucena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still didn't try it out locally, but checking out the code it looks good. Can you add this comment please? Thanks

@andreslucena andreslucena self-assigned this Feb 19, 2026
Copy link
Copy Markdown
Member

@andreslucena andreslucena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the fix:

Image

Thanks for the PR!

@andreslucena andreslucena merged commit 8291260 into decidim:develop Feb 19, 2026
77 checks passed
@alecslupu alecslupu added the no-backport Pull Requests that should not be backported label Feb 21, 2026
@alecslupu
Copy link
Copy Markdown
Contributor Author

Add no-backport as we do not have stimulus in 0.30.

@alecslupu alecslupu deleted the fix/taxonomies-trasnlations branch February 21, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module: admin module: core module: forms no-backport Pull Requests that should not be backported release: v0.31 Issues or PRs that need to be tackled for v0.31 type: fix PRs that implement a fix for a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Translating taxonomies items doesn't work

4 participants