Skip to content

fix(swipe): localize the base-layer label in the Layer Swipe panel (#860)#862

Merged
giswqs merged 3 commits into
mainfrom
fix/issue-860-swipe-basemap-i18n
Jun 25, 2026
Merged

fix(swipe): localize the base-layer label in the Layer Swipe panel (#860)#862
giswqs merged 3 commits into
mainfrom
fix/issue-860-swipe-basemap-i18n

Conversation

@giswqs

@giswqs giswqs commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #860. The Layer Swipe panel relabeled its grouped base layer with a hardcoded English "Background" (in swipe-style.ts, which runs outside the React tree and cannot call t()). In a non-English locale the panel showed Background while the main layer manager showed the translated label.

Changes

Route the translated label through the existing __GEOLIBRE_LAYER_LABELS__ / geolibre-layer-labels-change bridge, mirroring the existing setCompassLabel pattern for outside-React control strings:

  • packages/map/src/map-controller.ts: add a backgroundLabel field + setBackgroundLabel(), and publish a __basemap__ entry in publishLayerDisplayNames.
  • DesktopShell.tsx: push t("layers.background") to the controller on language change / controller re-init (sibling to the existing compass-label effect).
  • swipe-style.ts: read the __basemap__ label from the bridge, falling back to "Background" until it is published.

Testing

  • Reproduced in the running app under a temporary German layers.background translation: the swipe panel showed Background while the sidebar showed Hintergrund.
  • After the fix, the swipe panel base-layer label and tooltip read Hintergrund in German and Background in English, matching the sidebar. Verified in both light and dark themes (the temporary translation was reverted, so no locale catalog changes ship here).
  • Added a map-controller unit test asserting the __basemap__ label is published and updates on a label change.
  • Full frontend suite (1605 tests) and npm run build pass; pre-commit (eslint + npm build) clean.

Note: the separate on-map MapLibre LayerControl shows its own hardcoded Background; that is a different surface and out of scope here.

Summary by CodeRabbit

  • New Features
    • Base-layer (basemap) and layer-swipe labels now use the active language, showing the translated grouped basemap name instead of an English fallback.
    • Layer label text updates automatically when the map becomes ready and when the app language changes.
  • Bug Fixes
    • Fixed inconsistent basemap labeling in the layer swipe panel and related map UI by ensuring a single, consistently resolved basemap label source.

The Layer Swipe panel relabeled its grouped base layer with a hardcoded
English "Background", so a non-English locale showed "Background" in the
panel while the main layer manager showed the translated label.

Publish the translated base-layer label through the existing layer-display-name
bridge: the controller carries a `backgroundLabel` (set by DesktopShell from
`t("layers.background")` on language change) and includes a `__basemap__`
entry in `__GEOLIBRE_LAYER_LABELS__`. The swipe panel now reads that key,
falling back to "Background" until it is published.

Fixes #860
@netlify

netlify Bot commented Jun 25, 2026

Copy link
Copy Markdown

Deploy Preview for geolibre-app ready!

Name Link
🔨 Latest commit 6a41adc
🔍 Latest deploy log https://app.netlify.com/projects/geolibre-app/deploys/6a3ca169f9d3d40008486a95
😎 Deploy Preview https://deploy-preview-862--geolibre-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b66b71ef-5860-4796-82ee-e302aa902d2d

📥 Commits

Reviewing files that changed from the base of the PR and between ef6bfef and 6a41adc.

📒 Files selected for processing (2)
  • packages/map/src/map-controller.ts
  • tests/map-controller.test.ts

📝 Walkthrough

Walkthrough

The PR threads a translated background label from DesktopShell into MapController, publishes it as __GEOLIBRE_LAYER_LABELS__.__basemap__, and updates swipe label lookup and title formatting to use that value. Tests cover publishing and teardown behavior.

Changes

Basemap label localization

Layer / File(s) Summary
Base label publishing
packages/map/src/map-controller.ts, tests/map-controller.test.ts
MapController stores a background label, publishes it as __basemap__, clears the bridge on teardown, and the test suite verifies updates in a stubbed window environment.
Shell translation update
apps/geolibre-desktop/src/components/layout/DesktopShell.tsx
DesktopShell calls setBackgroundLabel with the translated background label when the translation function or map controller generation changes.
Swipe label resolution
apps/geolibre-desktop/src/lib/swipe-style.ts
Swipe label lookup and title formatting use the translated basemap label when present, with Background as the fallback.

Sequence Diagram(s)

sequenceDiagram
  participant DesktopShell
  participant MapController
  participant LayerLabels as window.__GEOLIBRE_LAYER_LABELS__
  participant syncSwipeLayerLabels

  DesktopShell->>MapController: setBackgroundLabel(t("layers.background"))
  MapController->>LayerLabels: publishLayerDisplayNames(...)
  MapController->>LayerLabels: clearLayerDisplayNames()
  syncSwipeLayerLabels->>LayerLabels: read "__basemap__"
  syncSwipeLayerLabels->>syncSwipeLayerLabels: use displayName in swipe title
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped from shell to map today,
and basemap labels learned to say
"Background" in each locale's tune,
then swiped along beneath the moon.
My ears go flick—what a label boon!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: localizing the Layer Swipe base-layer label.
Linked Issues check ✅ Passed The changes route the translated basemap label through the layer-label bridge and keep the swipe panel synced across locales.
Out of Scope Changes check ✅ Passed The diff stays focused on basemap label localization and its test coverage, with no unrelated features added.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-860-swipe-basemap-i18n

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.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Cloudflare Pages preview

Item Value
Preview https://9b21964b.geolibre-preview.pages.dev
Demo app https://9b21964b.geolibre-preview.pages.dev/demo/
Commit 119dcac

Comment thread tests/map-controller.test.ts Outdated
Comment thread packages/map/src/map-controller.ts
@github-actions

Copy link
Copy Markdown
Contributor

Code review

Overall the change is well-structured and closely mirrors the existing setCompassLabel pattern, making the intent easy to follow. The bridge approach (publish via __GEOLIBRE_LAYER_LABELS__ / geolibre-layer-labels-change) is the right tool for reaching outside-React code, and the useEffect hook in DesktopShell.tsx fires on both language change and controller re-init, which is correct. The fallback to "Background" in swipe-style.ts handles the brief window before the controller publishes its first label. No bugs or security issues found.

Bugs

None found.

Security

None found.

Performance

None found.

Quality

Finding File Confidence
Misleading test comment — "Defaults to English before the app pushes a translated label" implies the initial class-field value is being tested, but setBackgroundLabel("Background") is called explicitly first, so the default is not actually exercised by this assertion. tests/map-controller.test.ts:871-872 Medium
Object.fromEntries key-ordering: ["__basemap__", this.backgroundLabel] is placed before the spread, so a layer whose getNamedStyleLayers returns id "__basemap__" would silently override the translated label (last duplicate wins). Moving the entry after the spread removes the ambiguity. Unlikely to matter in practice since "__basemap__" is a synthetic key, but the ordering is a latent hazard. packages/map/src/map-controller.ts:1581-1588 Low

CLAUDE.md

The new t("layers.background") key is present in en.json (line 2169), and the effect correctly mirrors the existing setCompassLabel effect immediately above it — both consistent with the i18n conventions. No CLAUDE.md violations found.

- map-controller: publish the synthetic "__basemap__" label after the layer
  entries so it always wins an id collision (Object.fromEntries keeps the last
  occurrence), instead of being silently overridden by a layer sharing the id.
- map-controller test: reword the misleading comment that implied the class
  field default was covered; it tests an explicit English push, not the
  initializer.
Comment thread tests/map-controller.test.ts
Comment thread packages/map/src/map-controller.ts
@github-actions

Copy link
Copy Markdown
Contributor

Code review

Bugs

  • destroy() leaves stale __basemap__ entry in window.__GEOLIBRE_LAYER_LABELS__publishLayerDisplayNames([]) (called from destroy()) previously produced {} (clearing all labels). After this PR it always appends ["__basemap__", this.backgroundLabel], so after teardown the window global still carries {"__basemap__": "<last label>"}. Not harmful in practice (the swipe panel is removed alongside the controller and the label is re-published on re-init), but breaks the "clear on destroy" semantic. Confidence: high.

Quality

  • Test does not verify the geolibre-layer-labels-change event is dispatched — The new test asserts that __GEOLIBRE_LAYER_LABELS__ is written correctly, but the stub's dispatchEvent is never asserted on. The swipe panel only re-syncs its labels in response to that event (via its window.addEventListener("geolibre-layer-labels-change", scheduleEnhanceSwipePanel) wiring). A future regression that removes the dispatchEvent call would write the correct label but leave the panel stale — and the test would still pass. Confidence: high.

What was checked

  • The full diff in map-controller.ts, swipe-style.ts, DesktopShell.tsx, and tests/map-controller.test.ts.
  • Surrounding context: syncLayerspublishLayerDisplayNames call sites, destroy(), setCompassLabel (the pattern being mirrored), and the swipe panel's event-listener wiring in swipe-style.ts.
  • The i18n catalog (en.json) — layers.background key exists and resolves to "Background".
  • No security, performance, or CLAUDE.md issues found. The core fix is correct: the __basemap__ label is now published through the bridge, the swipe panel reads it with an appropriate fallback, and language changes push the updated translation via setBackgroundLabel.

- map-controller: restore the clear-on-destroy semantic. publishLayerDisplayNames
  now always emits the basemap entry (needed even with no overlay layers), so
  destroy() uses a dedicated clearLayerDisplayNames() that empties the bridge
  instead of leaving the last "__basemap__" label behind. (A layers.length
  guard would wrongly drop the basemap label in the common zero-overlay case.)
- map-controller test: track dispatched events in the window stub and assert the
  "geolibre-layer-labels-change" event fires on each publish, so a lost dispatch
  would fail the test; add a test that destroy() clears the bridge.
// and fires the event again so the panel re-syncs.
controller.setBackgroundLabel("Hintergrund");
assert.equal(win.__GEOLIBRE_LAYER_LABELS__?.__basemap__, "Hintergrund");
assert.equal(dispatched.length, 2);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor test-quality nit: the second dispatch assertion only checks the count but not the event name. The first check (assert.deepEqual(dispatched, ["geolibre-layer-labels-change"])) is strict; the second should be equally strict:

Suggested change
assert.equal(dispatched.length, 2);
assert.deepEqual(dispatched, [
"geolibre-layer-labels-change",
"geolibre-layer-labels-change",
]);

Also: the companion "clears published labels on destroy" test (withStubbedLabelWindow((win) => …)) ignores the dispatched array entirely, so it does not verify that clearLayerDisplayNames() fires (or does not fire) the change event on teardown. Consider threading in dispatched there too and asserting dispatched.length === 2 (one for setBackgroundLabel, one for destroy).

private clearLayerDisplayNames(): void {
if (typeof window === "undefined") return;
(window as GeoLibreLayerLabelWindow).__GEOLIBRE_LAYER_LABELS__ = {};
window.dispatchEvent(new CustomEvent("geolibre-layer-labels-change"));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Dispatching geolibre-layer-labels-change on teardown is deliberate (keeps the bridge consistent with the swipe panel if it is still visible), but it has a mild side-effect: the swipe panel's scheduleEnhanceSwipePanel listener fires a requestAnimationFrame callback after the controller is destroyed. The RAF runs harmlessly—document.querySelectorAll returns an empty NodeList when the panel is gone—but if the panel is still mounted at teardown time, it will briefly flash the fallback "Background" label before the new controller's first publish restores the translated value.

This matters only in the reinitialisation path (destroy → new controller created → mapReadyGeneration increments → setBackgroundLabel fires again). The RAF coalescing in scheduleEnhanceSwipePanel (if (swipeEnhanceFrame !== null) return) means the second publish's event typically wins if the two events land in the same frame. In practice the gap is sub-frame, so no visible flicker is expected. Just something to be aware of if a future test asserts on intermediate label state.

No code change needed—documenting the trade-off.

*/
setBackgroundLabel(label: string): void {
this.backgroundLabel = label;
this.publishLayerDisplayNames(this.syncedLayers);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One heads-up: setBackgroundLabel always calls publishLayerDisplayNames(this.syncedLayers), which iterates every synced layer and calls getNamedStyleLayers per layer. This is the same work that syncLayers does on every render, so it is not a new hot path—but it means a single label-push is O(n) in the layer count. If setBackgroundLabel were ever called in a tight loop (it currently is not—only on language-change or controller re-init), this could be noticeable with a large layer list. Not an issue today; just worth knowing if the call site ever changes.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Overall this is a clean, well-scoped fix. The approach mirrors the existing setCompassLabel bridge pattern exactly, the fallback in swipe-style.ts ensures no broken state before the first label push, and the clearLayerDisplayNames split keeps the destroy semantic correct. Nothing blocking—all findings are low severity.


Bugs

None found.


Security

None found. No user-controlled input enters the bridge; the label comes from t() which reads from static JSON catalogs.


Performance

  • O(n) re-publish on label push (confidence: low / not a real concern today): setBackgroundLabel calls publishLayerDisplayNames(this.syncedLayers), which iterates all synced layers. The call site is currently limited to language-change and controller re-init (infrequent), so this is fine. Noted inline on line 1692 in case the call site ever changes.

Quality

  • Test assertion strength (confidence: high, low severity): The second dispatch check in the first test case uses assert.equal(dispatched.length, 2) rather than assert.deepEqual. Also, the "clears on destroy" test ignores dispatched entirely and cannot catch a regression where clearLayerDisplayNames stops firing the change event. Suggestion included in the inline comment at line 890.
  • Teardown emits change event (confidence: high, informational): clearLayerDisplayNames dispatches geolibre-layer-labels-change on destroy(), which triggers a requestAnimationFrame in the swipe panel listener. The RAF runs harmlessly (empty NodeList if the panel is gone; fallback label if it is not), and the RAF coalescing prevents a double DOM write when the new controller publishes immediately after. No code change needed; noted inline at line 1603.

CLAUDE.md

  • The layers.background i18n key exists in en.json (line 2169) and is typed via the i18next.d.ts catalog binding—t("layers.background") is a compile-time-verified call. ✓
  • No direct commits to main; change arrives via PR. ✓
  • Other locale files (de.json, fr.json, …) do not yet contain a layers.background translation, so non-English users will see the English fallback "Background" in the swipe panel until those catalogs are updated. The PR description acknowledges this is intentional scope-bounding (the key is now wired; translations can follow separately). ✓ by design.

@giswqs giswqs merged commit 18c70c6 into main Jun 25, 2026
23 checks passed
@giswqs giswqs deleted the fix/issue-860-swipe-basemap-i18n branch June 25, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layer Swipe basemap label is hardcoded English ('Background') and not localized

1 participant