Skip to content

feat(add-data): internationalize the Add Data dialog (#427)#438

Merged
giswqs merged 3 commits into
mainfrom
feat/i18n-add-data-dialog
Jun 17, 2026
Merged

feat(add-data): internationalize the Add Data dialog (#427)#438
giswqs merged 3 commits into
mainfrom
feat/i18n-add-data-dialog

Conversation

@giswqs

@giswqs giswqs commented Jun 17, 2026

Copy link
Copy Markdown
Member

Summary

Internationalizes the entire Add Data dialog as one consistent unit (per the maintainer decision in #427), moving every hardcoded English string to react-i18next under a new addData.* namespace. en.json is the source of truth; other locales fall back to English until translated.

Follow-up to #423 / #417.

What changed

  • Shell (AddDataDialog.tsx + constants.ts): replaced KIND_LABELS / KIND_DESCRIPTIONS with a KIND_I18N_KEY map; the title/description now resolve via t("addData.kind.<key>.{label,description}").
  • Shared chrome (shared.tsx): layer name, insert-before (incl. optgroup labels and the default option), and footer (Add layer / Adding… / reused common.cancel).
  • Service library (ServiceLibrarySection.tsx): title, buttons, aria/title labels, the saved/removed/imported notices (with plural + interpolation), and the Uncategorized grouping label. The UNCATEGORIZED_LABEL constant stays as an internal grouping sentinel and is translated only at render.
  • All 11 source forms (Xyz, Wms, Wfs, Wmts, ArcGIS, Gpx, DelimitedText, Mbtiles, Postgres, Video, DeckViz): labels, placeholders, select options, button states, status notices, and the validation errors thrown in each submit handler. Shared strings (Service URL, Tile size, Source type, Choose file, Optional, Request failed with status {{status}}, …) live under addData.common.*; reuses common.save / common.cancel.

Notes / scope

  • Default layer names (e.g. "WMS Layer") now resolve through t(), so newly added layers get a localized default name (still user-editable).
  • Pure helpers in helpers.ts (parseRequiredNumber, parseVideoCorner, …) are intentionally left as-is: they are technical, framework-agnostic, and covered by unit tests asserting their English messages. The web-service format identifiers (PNG/JPEG) and numeric coordinate example placeholders are also left verbatim.
  • Typing is automatic via i18next.d.ts (keys checked against en.json).

Testing

  • npm run typecheck — no new type errors from this change. (There are 5 pre-existing, unrelated errors in packages/plugins/src/plugins/maplibre-basemap-control.ts about a basemapremove event not present in maplibre-gl-layer-control@0.16.0's types; confirmed identical on a clean main, so not introduced here.)
  • node --test over add-data-helpers, service-library, web-service-sync51/51 pass.
  • eslint: 0 errors in the touched files; pre-commit formatting hooks pass.

Summary by CodeRabbit

  • New Features
    • Added full internationalization for the “Add Data” workflow, including the dialog title/description, layer “kind” picker, all data source configuration screens (XYZ, WMS, WFS, WMTS, GPX, Delimited Text, MBTiles, ArcGIS, PostgreSQL, Video, Deck.gl), and the Saved Services library (headings, controls, notices, and validation/error messages).
  • Tests
    • Added a CI test that verifies the English i18n entries for every layer kind have non-empty label and description text.

Move all hardcoded English strings in the Add Data dialog to react-i18next
under a new addData.* namespace (en.json is the source of truth):

- Shell title/description (KIND_LABELS/KIND_DESCRIPTIONS -> addData.kind.*)
- Shared chrome in shared.tsx (layer name, insert-before, footer buttons)
- ServiceLibrarySection.tsx + Uncategorized grouping label
- Every web-service and file source form: labels, placeholders, select
  options, buttons, status notices, and validation errors

Reuses common.save/common.cancel where they exist. Other locales fall back
to English until translated.

Closes #427
@netlify

netlify Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploy Preview for geolibre-app ready!

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

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

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

@coderabbitai

coderabbitai Bot commented Jun 17, 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: c53b4230-859a-49b0-974b-e87dfc0f941f

📥 Commits

Reviewing files that changed from the base of the PR and between d1b8679 and b257b7e.

📒 Files selected for processing (6)
  • apps/geolibre-desktop/src/components/layout/add-data/ServiceLibrarySection.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/DeckVizSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/VideoSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/WfsSource.tsx
  • apps/geolibre-desktop/src/i18n/locales/en.json
  • tests/add-data-i18n.test.ts

📝 Walkthrough

Walkthrough

All hardcoded English strings in the "Add Data" workflow are replaced with react-i18next t() calls across the dialog, shared form components, ServiceLibrarySection, and all eleven data-source components. KIND_LABELS/KIND_DESCRIPTIONS are removed in favor of a new KindI18nKey type and KIND_I18N_KEY mapping, and 268 new English locale entries are added to en.json.

Changes

Add Data workflow i18n migration

Layer / File(s) Summary
i18n key contract and locale strings
src/components/layout/add-data/constants.ts, src/i18n/locales/en.json
Removes KIND_LABELS/KIND_DESCRIPTIONS; adds KindI18nKey string-union type and KIND_I18N_KEY record mapping each AddDataKind to an i18n segment. Adds 268 new entries under common.addData in en.json covering all source-type labels/descriptions, shared form strings, service library UI, and per-source form/validation/error text.
AddDataDialog, shared form fields, and ServiceLibrarySection
src/components/layout/AddDataDialog.tsx, src/components/layout/add-data/shared.tsx, src/components/layout/add-data/ServiceLibrarySection.tsx
AddDataDialog computes title/description via t() keyed on KIND_I18N_KEY[kind]. Shared LayerNameField, InsertBeforeField, and AddDataFooter render all labels and button text from translation keys. ServiceLibrarySection localizes section heading, all button aria-label/title attributes, the service picker, empty-state, save/delete/export/import notices, and every error message.
XYZ, WMS, WMTS, WFS, ArcGIS, Video source components
src/components/layout/add-data/sources/XyzSource.tsx, src/components/layout/add-data/sources/WmsSource.tsx, src/components/layout/add-data/sources/WmtsSource.tsx, src/components/layout/add-data/sources/WfsSource.tsx, src/components/layout/add-data/sources/ArcGISSource.tsx, src/components/layout/add-data/sources/VideoSource.tsx
Each component adds useTranslation, switches default layer names and validation/error strings to t() calls, and replaces all form labels, placeholders, and select option text with translation keys.
GPX, DelimitedText, MBTiles, PostgreSQL, and DeckViz source components
src/components/layout/add-data/sources/GpxSource.tsx, src/components/layout/add-data/sources/DelimitedTextSource.tsx, src/components/layout/add-data/sources/MbtilesSource.tsx, src/components/layout/add-data/sources/PostgresSource.tsx, src/components/layout/add-data/sources/DeckVizSource.tsx
The more complex sources are fully localized: file/URL input flows, multi-step status messages, connection lifecycle text (PostgreSQL), layer-type checkbox captions (GPX), delimiter options (DelimitedText), style-control labels, and all error throws in DeckVizSource are switched from hardcoded English to t() calls.
Add Data i18n validation test
tests/add-data-i18n.test.ts
Adds a test that loads en.json and verifies that every KIND_I18N_KEY entry has a corresponding addData.kind.<key>.label and addData.kind.<key>.description entry with non-empty strings, ensuring contract compliance at build time.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • opengeos/GeoLibre#249 — Introduces the deckgl-viz Add Data kind to the AddDataKind constants and dialog, which this PR then populates with full i18n coverage including the kind label/description and all Deck.gl source component strings.
  • opengeos/GeoLibre#284 — Introduces the react-i18next framework and typed catalog keys that this PR builds on to migrate the Add Data UI and extend en.json.
  • opengeos/GeoLibre#325 — Adds the scenegraph layer kind and UI for Deck.gl 3D model support, which this PR then localizes through the Add Data i18n plumbing (KIND_I18N_KEY entries and DeckVizSource text).

Poem

🐇 Hop, hop, no more English hard-coded in sight,
Every label and error now speaks in t()'s light.
KIND_LABELS is gone, KIND_I18N_KEY is here,
Two hundred sixty-eight strings now perfectly clear.
From XYZ tiles to Deck.gl's grand show,
The bunny says: parlez-vous GeoLibre? — Oui, we know! 🌍

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% 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 summarizes the main change: internationalizing the Add Data dialog. It directly reflects the primary objective of migrating hardcoded English strings to react-i18next.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/i18n-add-data-dialog

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

Comment thread apps/geolibre-desktop/src/components/layout/AddDataDialog.tsx
Comment thread apps/geolibre-desktop/src/components/layout/add-data/sources/DeckVizSource.tsx Outdated
Comment thread apps/geolibre-desktop/src/i18n/locales/en.json Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Code review

This is a large, mechanical i18n migration (662 additions, 281 deletions across 14 files). The approach is sound: en.json as the source of truth, a typed KindI18nKey map, re-use of addData.common.* for shared strings, and proper plural keys (_one/_other) for count-carrying messages. The test suite passes and no new type errors are introduced. The four findings below are all quality/maintainability concerns; no bugs that affect current English-locale behaviour were found.


Bugs

None found.

Security

None found.

Performance

None found.

Quality

# Finding Location Confidence
1 Template literal bypasses i18n type checking. t(`addData.kind.${KIND_I18N_KEY[kind]}.label`) is opaque to TypeScript and i18next's generated types — a missing or renamed .label/.description key in en.json would only surface at runtime as fallback text, not a compile error. The KindI18nKey union guards the segment, but not the full key path. See inline comment. AddDataDialog.tsx:98–101 High
2 Non-translatable separator and trailing period in DeckViz status. The interpunct · and . are hard-coded in JSX rather than in the translation string, preventing locale-appropriate punctuation. Also inconsistent: loadedFeatures includes the period inside the translation value, while the tabular branch puts it in JSX — a translator who follows the loadedFeatures pattern would produce a double period. DeckVizSource.tsx:310–314 Medium
3 importedDropped uses count (a magic plural variable) without plural variants. i18next attempts to resolve importedDropped_one/importedDropped_other first; it currently falls back to the base key only because those variants are absent. A future translator who adds them (natural, seeing {{count}}) would silently change the resolution path and strand the base key. Renaming {{count}}{{dropped}} (or adding explicit plural variants now) makes the intent unambiguous. en.json:106 Medium
4 Stale default-name sentinel in file-pick callbacks. current !== t("addData.delimitedText.defaultName") compares the stored layer name against a live translation. If the locale changes between dialog open and file pick, current holds the old locale's default string while t() returns the new one, so the condition incorrectly treats the un-edited default as a custom name and skips the auto-rename. Same pattern in GpxSource and MbtilesSource. Capturing the default once at render time (const defaultName = t(...) before useAddDataSource) avoids the mismatch. DelimitedTextSource.tsx:120–128 Low

CLAUDE.md

  • i18n conventions followed: All new user-facing strings use t(), en.json is the source of truth, catalogs for other locales fall back to English. Consistent with docs/i18n.md guidelines. ✓
  • No direct commits to main: PR branched and reviewed. ✓
  • The five pre-existing type errors in packages/plugins noted in the PR description are confirmed unrelated to this change.

- DeckVizSource: lift the "·" separator and trailing period out of the JSX
  template into a translatable `addData.deckViz.loadedTabular` wrapper key, so
  translators control word order/punctuation and the row/column fragments stay
  correctly pluralized (loadedRows/loadedColumns now carry no "Loaded" prefix).
- ServiceLibrarySection: rename the `importedDropped` interpolation var from
  `count` to `dropped` so i18next does not attempt plural resolution on a key
  with no plural variants.
- DelimitedText/Gpx/Mbtiles sources: capture the default layer name once via a
  lazy `useState` so the "did the user rename it?" comparison stays stable even
  if the UI language changes mid-session.
- Add tests/add-data-i18n.test.ts: asserts every KIND_I18N_KEY maps to a
  label+description in en.json, catching renamed/removed dialog kind subtrees at
  CI time (the dialog resolves these via a runtime-interpolated t() key that the
  type checker cannot validate).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/add-data-i18n.test.ts`:
- Around line 32-38: The assertions for entry.label and entry.description allow
whitespace-only strings to pass validation. Modify the assert.ok() calls to trim
the values before checking them for truthiness. For both entry.label and
entry.description, apply the trim() method before passing them to assert.ok() so
that strings containing only whitespace will properly fail the assertion and
enforce that translations contain meaningful content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 02ac5e30-f6cb-44e0-a7bf-6dfd3b181de2

📥 Commits

Reviewing files that changed from the base of the PR and between 6a2c10a and d1b8679.

📒 Files selected for processing (7)
  • apps/geolibre-desktop/src/components/layout/add-data/ServiceLibrarySection.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/DeckVizSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/DelimitedTextSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/GpxSource.tsx
  • apps/geolibre-desktop/src/components/layout/add-data/sources/MbtilesSource.tsx
  • apps/geolibre-desktop/src/i18n/locales/en.json
  • tests/add-data-i18n.test.ts

Comment thread tests/add-data-i18n.test.ts Outdated
Comment thread apps/geolibre-desktop/src/i18n/locales/en.json Outdated
Comment thread apps/geolibre-desktop/src/components/layout/add-data/sources/WfsSource.tsx Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Code review

This PR cleanly internationalizes the entire Add Data dialog as one consistent unit. The approach — a new addData.* namespace in en.json, a KIND_I18N_KEY indirection map, useState(() => t(...)) to freeze the default name for "did the user rename it?" comparisons, and a CI test guarding the dynamically-interpolated kind keys — is solid. Findings below are grouped by category; nothing blocks merge, but two of the items below are user-visible gaps worth a follow-up.


Bugs / Logic errors

  • Untranslated validation errors from helper labels (medium confidence): parseOptionalNumber(wfsMaxFeatures, "max features") in WfsSource.tsx:56 can throw "Enter a numeric max features." to non-English users. The same applies to parseVideoCorner calls in VideoSource.tsx:49-52, which produce messages like "top-left longitude must be between -180 and 180.". The helper bodies are intentionally left untouched per the PR description, but the labels are caller-supplied and could be translated there. Inline comment posted on WfsSource.tsx.

Quality / Maintainability

  • importedDropped leading-space pattern is a translator trap (medium confidence): The value " ({{dropped}} skipped — library full)" carries a leading space so it appends cleanly in English, but that space is invisible in a JSON editor and causes double-spacing or missing spacing if a translator reorders the suffix. Two self-contained plural keys (imported / importedWithDropped) would be safer. Inline comment posted on en.json:104-106.

  • Nested t() calls pass pre-translated strings as interpolation values (medium confidence): loadedTabular receives {{rows}} and {{columns}} already pluralised (e.g. "3 rows"). This locks the separator · and the word Loaded inside loadedTabular, preventing translators from adjusting phrasing based on the underlying counts. Works in English; a limitation for future locale work. Inline comment posted on DeckVizSource.tsx:308-316.

  • Test only guards addData.kind.* keys (low confidence): add-data-i18n.test.ts is exactly the right guard for the one dynamically-interpolated key path. Other keys — plurals like retrievedColumns_one, interpolation keys like requestFailed, the importedDropped suffix — are not validated at test time; a typo would surface only at runtime. Worth extending incrementally.

  • workspaceLayerPlaceholder is a GIS protocol token (low confidence): The PR description explicitly keeps format identifiers (PNG/JPEG) and coordinate example placeholders verbatim; workspace:layer is similarly a technical token that translators would typically leave unchanged. Translating it via t() is consistent with the overall approach but slightly at odds with the stated scope. Nit only.

Security: Nothing found.

Performance: Nothing found.

CLAUDE.md: No violations. The PR uses t() for all new user-facing strings, treats en.json as the source of truth, and reuses common.save / common.cancel where they exist — consistent with the i18n conventions in docs/i18n.md referenced by CLAUDE.md.

- tests/add-data-i18n.test.ts (CodeRabbit): trim label/description before the
  non-empty assertion so whitespace-only translations fail.
- ServiceLibrarySection + en.json (Claude): replace the fragile
  imported + importedDropped suffix-concatenation with two self-contained plural
  keys (imported / importedWithDropped), so each notice is a complete sentence a
  translator can reorder freely.
- WfsSource (Claude): translate the "max features" numeric validation error
  (addData.wfs.errorMaxFeaturesNumeric) via an inline check instead of the
  English-only parseOptionalNumber helper label.
- VideoSource (Claude): parse the four corners with a local translated
  parseCorner (addData.video.errorCorner* + corner-name keys) so the corner
  validation errors are localized.
- DeckVizSource (Claude): document that loadedTabular interpolates pre-pluralized
  {{rows}}/{{columns}} fragments; kept the nested-t() composition so each count
  keeps its own i18next plural rules (a 4-key rowOne/colOne form would regress
  to "=== 1" and break languages with >2 plural forms).
@giswqs giswqs merged commit 89d5d72 into main Jun 17, 2026
28 checks passed
@giswqs giswqs deleted the feat/i18n-add-data-dialog branch June 17, 2026 16:55
Comment on lines +39 to +40
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
throw new Error(t("addData.video.errorCornerNumber", { corner }));

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.

Bug: combined lng/lat NaN check loses error precision

The original parseVideoCorner used parseRequiredNumber separately for longitude and latitude, so the error message pinpointed which coordinate was bad ("Enter a numeric top-left longitude" vs. "Enter a numeric top-left latitude"). The combined check here always emits "Enter a numeric {{corner}} longitude and latitude" even when only one of the two values is NaN.

Suggested change
if (!Number.isFinite(lng) || !Number.isFinite(lat)) {
throw new Error(t("addData.video.errorCornerNumber", { corner }));
if (!Number.isFinite(lng)) {
throw new Error(t("addData.video.errorCornerNumber", { corner, coord: t("addData.video.coordLng") }));
}
if (!Number.isFinite(lat)) {
throw new Error(t("addData.video.errorCornerNumber", { corner, coord: t("addData.video.coordLat") }));
}

(Requires adjusting the errorCornerNumber key to "Enter a numeric {{corner}} {{coord}}." and adding "coordLng": "longitude" / "coordLat": "latitude" to en.json, or just splitting into two dedicated keys like errorCornerLngNaN / errorCornerLatNaN.)

"loadPlaceholder": "Load a saved service…",
"builtin": "{{name}} (built-in)",
"deleteAria": "Delete saved service",
"empty": "No saved services yet — fill in the form and choose “Save current”.",

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.

Quality: empty string hardcodes the saveCurrent button label

The phrase "Save current" (in curly quotes) is the button's label — but that label is separately translatable as addData.serviceLibrary.saveCurrent. If a locale renders the button as anything other than "Save current", this prose message will be out of sync with the actual UI.

Suggested change
"empty": "No saved services yet — fill in the form and choose “Save current.",
"empty": "No saved services yet — fill in the form and save the current settings.",

Alternatively, omit the specific button reference entirely ("No saved services yet.") so the string doesn't depend on knowing how another key is translated.

DEFAULT_VIDEO_WEBM_URL,
} from "../constants";
import { createBaseLayer, parseVideoCorner } from "../helpers";
import { createBaseLayer } from "../helpers";

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.

Nit: parseVideoCorner is now a dead production export

Dropping parseVideoCorner from the import here means helpers.ts:parseVideoCorner is no longer called from any production file — it's only imported by tests/add-data-helpers.test.ts. The same applies to parseOptionalNumber after the WfsSource change. Neither is broken (the tests keep the contracts alive), but a quick JSDoc on those two helpers clarifying their test-only status would save a future reader from wondering why they exist.

Comment on lines +316 to +319
t("addData.deckViz.loadedTabular", {
rows: t("addData.deckViz.loadedRows", { count: parsed.rowCount }),
columns: t("addData.deckViz.loadedColumns", {
count: parsed.columns.length,

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.

Quality: loadedTabular composition is an unchecked inter-key dependency

loadedTabular silently requires loadedRows and loadedColumns to be present and returning sensible strings — TypeScript's typed t() won't flag a missing or renamed loadedRows/loadedColumns key because the template is constructed at runtime. The inline comment explains the design well, but this dependency isn't covered by the new add-data-i18n.test.ts (which only guards addData.kind.*).

Consider adding a small guard in add-data-i18n.test.ts that confirms all three keys exist and produce non-empty output at count=1 and count=2, similar to the kind-label test already there.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Overall this is a clean, well-scoped i18n migration. The kind-label indirection via KIND_I18N_KEY, useState(() => t(...)) to freeze the default name where file-pickers need stable comparison, and the new CI guard test are all solid choices. Four findings below — one bug, three quality notes.


Bugs

1. VideoSource.tsx:39–40 — combined lng/lat NaN check loses error precision (high confidence)

parseCorner tests both coordinates in a single branch and always emits "Enter a numeric {{corner}} longitude and latitude". The original parseVideoCorner used parseRequiredNumber separately for each, so the message could say just "longitude" (without "and latitude") when only that value was invalid. Fix: split the combined || check into two separate if blocks — one for lng, one for lat — with distinct i18n keys or a {{coord}} interpolation variable.


Quality

2. en.json:92"empty" string hardcodes the saveCurrent button label (medium-high confidence)

The prose "…choose "Save current"" mirrors addData.serviceLibrary.saveCurrent, but both strings are independently translated. A locale that renders the button differently will have a mismatch in the empty-state prose. Removing the button-name reference (e.g. "No saved services yet — fill in the form and save a service.") avoids the coupling.

3. VideoSource.tsx:12 / WfsSource.tsx:9parseVideoCorner and parseOptionalNumber are now dead production exports (medium confidence)

After this PR, no production file imports either helper (they're only consumed by tests/add-data-helpers.test.ts). The tests keep the contracts alive, but a JSDoc note on each function in helpers.ts (e.g. @remarks Only imported by unit tests after VideoSource/WfsSource migrated to localised validation.) would prevent a future reader from wondering why they're exported.

4. DeckVizSource.tsx:316–319loadedTabular composition is an unchecked inter-key dependency (medium confidence)

The pattern t("loadedTabular", { rows: t("loadedRows", {count}), columns: t("loadedColumns", {count}) }) relies on loadedRows/loadedColumns existing, but neither TypeScript's typed t() nor the new add-data-i18n.test.ts guard these keys. A silent rename of loadedRows would render the key name literally at runtime. Consider adding a small check in add-data-i18n.test.ts that exercises all three keys with count=1 and count=2.


Performance / Security

Nothing worth raising.

CLAUDE.md

No violations. Branch/PR convention followed, en.json is the source of truth, all new user-facing strings use t().

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.

1 participant