Skip to content

fix(admin): replace hardcoded German strings with i18n keys (#7735)#7736

Merged
JohnMcLear merged 4 commits into
ether:developfrom
JohnMcLear:fix/admin-i18n-hardcoded-strings
May 12, 2026
Merged

fix(admin): replace hardcoded German strings with i18n keys (#7735)#7736
JohnMcLear merged 4 commits into
ether:developfrom
JohnMcLear:fix/admin-i18n-hardcoded-strings

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

Closes #7735.

PR #7716 (admin design rework) shipped the new admin UI with ~50+ literal German strings baked into JSX. Existing <Trans i18nKey="…"/> calls resolve to whatever language i18next detects (French, English, etc.), but the literals stay German — producing the "mix of French, English and German-ish" salad #7735 reports.

This PR:

  • Adds 90+ keys to src/locales/en.json (admin.*, admin_login.*, admin_pads.*, admin_plugins.*, admin_plugins_info.*, admin_settings.*, admin_shout.*, and the previously-orphaned update.page.{disabled,unauthorized,error}).
  • Replaces every hardcoded literal in admin/src/{App,LoginScreen,HomePage,HelpPage,PadPage,SettingsPage,ShoutPage}.tsx with t() or <Trans>.
  • Threads i18n.language into PadPage so relativeTime() and toLocale*() honour the user's locale instead of forcing de-DE.

Non-EN locales pick up translations from translatewiki on its normal round-trip; until then i18next falls back to en.json.

Test plan

  • pnpm --filter ep_etherpad-lite vitest run tests/backend-new/specs/admin-i18n-source-lint.test.ts — 10 source-lint assertions (10 pass).
  • pnpm --filter admin tsc --noEmit — clean.
  • pnpm --filter admin vite build — builds.
  • Playwright admin spec (tests/frontend-new/admin-spec/admini18n.spec.ts) extended with rendered-text assertions for Home, Pads, Help, Login in EN; CI runs.
  • Visually confirm /admin/?lng=en and /admin/?lng=de after merge.

🤖 Generated with Claude Code

…r#7735)

PR ether#7716 ("chore: fixed admin design rework") rebuilt admin/src/pages
with literal German copy inline — "Update verfügbar", "Aktualisieren",
"Keine Pads gefunden", "Hook-Bindings", "de-DE" date formatters, etc.
Non-DE users see a French/English/German salad: <Trans i18nKey="…"/>
calls resolve correctly via translatewiki, but every literal stays
German regardless of browser locale.

This change:

  - Adds 90+ keys to src/locales/en.json under admin.*, admin_login.*,
    admin_pads.*, admin_plugins.*, admin_plugins_info.*, admin_settings.*,
    admin_shout.*, and the previously-orphaned update.page.{disabled,
    unauthorized,error}.
  - Replaces every hardcoded literal in admin/src/{App,LoginScreen,
    HomePage,HelpPage,PadPage,SettingsPage,ShoutPage,UpdatePage}.tsx with
    t() or <Trans>.
  - Threads i18n.language into PadPage so relativeTime() and
    toLocale*() honour the user's locale instead of forcing de-DE.

Test coverage:

  - src/tests/backend-new/specs/admin-i18n-source-lint.test.ts (vitest):
    scans admin/src/pages/*.tsx + App.tsx for a denylist of German
    literals introduced by ether#7716, asserts PadPage no longer hardcodes
    'de-DE', and pins the set of new en.json keys.
  - src/tests/frontend-new/admin-spec/admini18n.spec.ts (Playwright):
    extended to assert rendered English text on every page (Home, Pads,
    Help, Login) and verify no German leakage on the English path.

Non-EN locales pick up translations from translatewiki on its normal
cadence; until then i18next falls back to en.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Replace hardcoded German strings with i18n keys in admin UI

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replaces ~50 hardcoded German strings with i18n keys across admin pages
• Adds 90+ translation keys to en.json for admin UI components
• Threads i18n.language into PadPage for locale-aware date/time formatting
• Adds source-level lint test to prevent German literal regressions
• Extends Playwright admin spec with rendered text assertions
Diagram
flowchart LR
  A["Admin Pages<br/>App, LoginScreen, HomePage,<br/>HelpPage, PadPage, etc."] -->|"Replace hardcoded<br/>German literals"| B["t() and Trans<br/>i18n calls"]
  B -->|"Reference"| C["en.json<br/>90+ new keys"]
  D["PadPage"] -->|"Thread i18n.language"| E["Locale-aware<br/>formatters"]
  F["Source Lint Test<br/>admin-i18n-source-lint.test.ts"] -->|"Catch regressions"| G["CI validation"]
  H["Playwright Spec<br/>admini18n.spec.ts"] -->|"Assert rendered<br/>English text"| I["Rendered output<br/>verification"]
Loading

Grey Divider

File Changes

1. src/tests/backend-new/specs/admin-i18n-source-lint.test.ts 🧪 Tests +92/-0

New source-level lint test for German literals

src/tests/backend-new/specs/admin-i18n-source-lint.test.ts


2. src/tests/frontend-new/admin-spec/admini18n.spec.ts 🧪 Tests +90/-14

Extended Playwright spec with rendered text assertions

src/tests/frontend-new/admin-spec/admini18n.spec.ts


3. admin/src/App.tsx ✨ Enhancement +4/-4

Replace hardcoded strings with i18n keys

admin/src/App.tsx


View more (7)
4. admin/src/pages/HelpPage.tsx ✨ Enhancement +12/-12

Internationalize help page copy and labels

admin/src/pages/HelpPage.tsx


5. admin/src/pages/HomePage.tsx ✨ Enhancement +18/-19

Internationalize plugin manager page strings

admin/src/pages/HomePage.tsx


6. admin/src/pages/LoginScreen.tsx ✨ Enhancement +9/-7

Add i18n to login form labels and messages

admin/src/pages/LoginScreen.tsx


7. admin/src/pages/PadPage.tsx ✨ Enhancement +61/-61

Internationalize pads page and thread locale formatting

admin/src/pages/PadPage.tsx


8. admin/src/pages/SettingsPage.tsx ✨ Enhancement +4/-3

Add i18n to settings page toast messages

admin/src/pages/SettingsPage.tsx


9. admin/src/pages/ShoutPage.tsx ✨ Enhancement +5/-3

Internationalize communication page strings

admin/src/pages/ShoutPage.tsx


10. src/locales/en.json ✨ Enhancement +94/-0

Add 90+ new admin UI translation keys

src/locales/en.json


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 12, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Context used

Grey Divider


Remediation recommended

1. Unsafe locale into Intl 🐞 Bug ☼ Reliability
Description
PadPage uses i18n.language directly as the locale argument to
toLocaleString()/toLocaleDateString()/toLocaleTimeString(), which can throw a RangeError and
break rendering if the detected language is malformed (for example via a crafted ?lng= value).
This is user-controlled in the admin SPA because language detection is driven by
i18next-browser-languagedetector.
Code

admin/src/pages/PadPage.tsx[R348-353]

+                      <td className="pm-num">{pad.revisionNumber.toLocaleString(locale)}</td>
                      <td>
                        <div className="pm-time">
-                          <span className="pm-time-rel">{relativeTime(pad.lastEdited)}</span>
-                          <span className="pm-time-abs">{fmtDate(pad.lastEdited)}</span>
+                          <span className="pm-time-rel">{relativeTime(t, pad.lastEdited)}</span>
+                          <span className="pm-time-abs">{fmtDate(locale, pad.lastEdited)}</span>
                        </div>
Evidence
Language detection is handled by i18next’s browser LanguageDetector, and the resulting
i18n.language is used as the locale argument to Intl formatters in PadPage. Because the detected
language can be influenced via ?lng=..., a malformed value can propagate into Intl APIs and throw
at runtime.

admin/src/localization/i18n.ts[55-64]
src/tests/frontend-new/admin-spec/admini18n.spec.ts[37-41]
admin/src/pages/PadPage.tsx[30-36]
admin/src/pages/PadPage.tsx[44-46]
admin/src/pages/PadPage.tsx[348-353]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PadPage` passes `i18n.language` directly to `toLocaleString()` / `toLocaleDateString()` / `toLocaleTimeString()`. If `i18n.language` contains a malformed locale tag (e.g. `en_US`), the Intl methods can throw `RangeError`, causing the `/admin/pads` page to fail to render.

### Issue Context
`i18n.language` is influenced by `i18next-browser-languagedetector` (including URL `?lng=`), and is not validated/sanitized before being used as an Intl locale.

### Fix Focus Areas
- admin/src/pages/PadPage.tsx[348-353]

### Suggested fix
1. Introduce a small helper to produce a safe locale:
  - Normalize common bad forms (e.g., replace `_` with `-`).
  - Validate via a guarded Intl check (try/catch around `Intl.DateTimeFormat.supportedLocalesOf()`), falling back to `undefined` or `'en'`.
2. Use the sanitized locale for all `toLocale*()` calls (and keep `relativeTime()` using `t()` as it is).

Example approach (TypeScript):
```ts
const sanitizeLocale = (lng?: string) => {
 const raw = (lng ?? '').trim();
 if (!raw) return 'en';
 const normalized = raw.replace(/_/g, '-');
 try {
   // Will throw on structurally invalid tags
   const supported = Intl.DateTimeFormat.supportedLocalesOf([normalized]);
   return supported[0] ?? 'en';
 } catch {
   return 'en';
 }
};

const locale = sanitizeLocale(i18n.resolvedLanguage ?? i18n.language);
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

JohnMcLear and others added 3 commits May 12, 2026 09:40
Pre-rework admin already had:
  ep_admin_pads:ep_adminpads2_action       ("Action")
  ep_admin_pads:ep_adminpads2_last-edited  ("Last edited")
  ep_admin_pads:ep_adminpads2_no-results   ("No results")

Initial pass added admin_pads.{col.action, col.last_edited,
sort.last_edited, empty_state} duplicating those — drop the duplicates
from en.json and point PadPage.tsx at the existing translatewiki-fed
keys. Stats/column heads that genuinely didn't exist before
(admin_pads.col.{pad,users,revisions}, the filter chips, relative-time,
pagination, etc.) stay as new keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qodo flagged a reliability bug in PadPage on PR ether#7736: i18n.language
flows from user-controlled ?lng= straight into Intl.* formatters, which
throw RangeError on malformed tags (e.g. 'en_US', '💥'). Crashing the
pads page on a crafted URL.

Wrap the locale in a sanitizeLocale() helper that normalises '_' → '-'
and validates via Intl.DateTimeFormat.supportedLocalesOf(), falling back
to 'en' so dates render in a sane locale rather than the user's browser
default fighting page copy.

Same audit surfaced four additional regressions from ether#7716 still on
develop, fixed here on-theme:

  - HomePage dropped <a href="https://npmjs.com/..."> wrappers on both
    installed and available plugin rows. Restored with .pm-plugin-link.
  - "Downloads" column / "Most popular" default sort / "Popular" tag
    were dead UI — src/static/js/pluginfw/installer.ts::search() never
    populates `downloads`. Removed the column, default sort, and tag;
    dropped `downloads` from PluginDef + SearchParams.sortBy.
  - PadPage sort dropdown hardcoded `ascending: e.target.value ===
    'padName'`, leaving no way to invert direction. Replaced with a
    paired ↑/↓ button (.pm-sort-dir) for both HomePage and PadPage.
  - "1 Core" stat hint hardcoded count=1. Derived from
    installedPlugins.filter(p => p.name === 'ep_etherpad-lite').length.
  - Deleted orphan modules SearchField.tsx and sorting.ts (no longer
    imported anywhere after ether#7716).

Tests:

  - admin-i18n-source-lint.test.ts: +3 assertions (sanitizeLocale
    pattern, dead-downloads check, orphan-module deletion, sort-dir
    toggle) → 14 passing.
  - admini18n.spec.ts: +2 assertions (npmjs link on ep_etherpad-lite
    row, sort-direction toggle visible).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR ether#7716 ("admin design rework") shipped ~50 hardcoded German literals,
dropped npmjs.com link affordances, removed the sort-direction control
on PadPage, and forced `de-DE` into Intl formatters — none of which the
AGENTS.MD guide explicitly forbade. Document the rules so the next UI
refresh cannot regress these in the same way:

- i18n section spells out which slots must be localised (JSX text,
  placeholders, titles, aria-labels, alts, toasts, options, alerts),
  which API to use per surface (<Trans>/t() in React, data-l10n-id in
  the legacy pad UI, never window._ rebound), where keys live
  (src/locales/en.json — never hand-edit non-EN locales), to reuse
  existing keys before duplicating, pluralisation via _one/_other,
  defaultValue is safety not a substitute, and points at the
  source-lint test that enforces the denylist.

- a11y section spells out the lessons surfaced by the audit: icon-only
  buttons need aria-label AND title (both localised), sort controls
  must be focusable + reversible, semantic HTML over div soup, external
  navigation is <a>, "don't drop affordances when restyling" is a
  hard rule, Playwright specs must assert rendered strings + at least
  one structural affordance for UI changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JohnMcLear JohnMcLear merged commit ff7c4d5 into ether:develop May 12, 2026
19 checks passed
JohnMcLear added a commit to JohnMcLear/etherpad that referenced this pull request May 13, 2026
…#7666)

Takes over ether#7666 / closes ether#7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework ether#7716
and admin i18n fixes ether#7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).

Highlights:

- New parsed JSONC settings editor under
  admin/src/components/settings/ — FormView, ModeToggle,
  ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
  number, bool, null, env pill), and pure helpers (comments,
  envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
  that round-trip through the raw textarea (env-pill spec asserts
  this; docker-template spec protects against form-view degradation
  on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
  inlined at build time via vite (drops the runtime fs.allow
  widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
  /admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
  edits that preserve key order and trailing comments (stops at
  end-of-line so trailing-comment trains don't bleed into the next
  property).
- i18n keys added for form mode, parse error, env pill,
  default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
  raw round-trip, and form-mode regressions called out in ether#7666
  review (stable React keys from AST offsets, save-toast on server
  ack only, NumberInput draft sync, parse-error flash during
  initial load, .settings CSS conflict resolution, focus retention
  via rAF, IconButton type defaulting to 'button').

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit to JohnMcLear/etherpad that referenced this pull request May 15, 2026
…rPage imports them

PR ether#7736 added a lint assertion that admin/src/components/SearchField.tsx
and admin/src/utils/sorting.ts must NOT exist, on the assumption they
were dead code after ether#7716. They aren't — the GDPR AuthorPage (ether#7667)
imports both. The previous commit restored the files; this commit flips
the assertion so the test:

  - verifies both files exist
  - verifies admin/src/pages/AuthorPage.tsx still imports them

If a future cleanup wants to delete these modules, the test now forces
the author to also delete or refactor the AuthorPage consumption first,
preventing a repeat of the merge-order accident that produced this bug.

Test plan:
  - cd src && pnpm exec vitest run tests/backend-new/specs/admin-i18n-source-lint.test.ts
    → 14 passed (14)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit that referenced this pull request May 15, 2026
…7746)

* fix(admin): restore SearchField + sorting modules used by AuthorPage

PR #7736 ("replace hardcoded German strings with i18n keys") deleted
admin/src/components/SearchField.tsx and admin/src/utils/sorting.ts as
"orphan modules (no longer imported anywhere after #7716)". At that
point, the GDPR admin AuthorPage (PR #7667) had not yet landed on
develop. When #7667 merged afterwards, the new admin/src/pages/AuthorPage.tsx
brought back imports of SearchField and determineSorting — but the
files were already gone, leaving develop with broken admin imports.

This breaks `pnpm --filter admin run build-copy` on every CI job that
builds the admin UI:

  src/pages/AuthorPage.tsx(6,27): error TS2307: Cannot find module
    '../components/SearchField.tsx' or its corresponding type
    declarations.
  src/pages/AuthorPage.tsx(9,32): error TS2307: Cannot find module
    '../utils/sorting.ts' or its corresponding type declarations.
  src/pages/AuthorPage.tsx(207,29): error TS7006: Parameter 'v'
    implicitly has an 'any' type.

Backend tests, Frontend admin tests, Docker (build-test,
build-test-local-plugin, build-test-db-drivers), rate limit, and
Upgrade from latest release all fail at the admin build step on
develop.

Restore both files verbatim from before the deletion (commit ff7c4d5^).
With them back in place AuthorPage.tsx's existing imports resolve, the
implicit-any on the SearchField onChange callback goes away (typed via
SearchFieldProps), and `pnpm --filter admin run build-copy` succeeds.

This is the minimal, surgical fix; whether SearchField / determineSorting
should ultimately be inlined into AuthorPage is a separate cleanup.

Test plan:
  - cd admin && pnpm exec tsc --noEmit        → no errors
  - cd admin && pnpm exec tsc && pnpm exec vite build → built in 545ms

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(admin-i18n-lint): invert orphan-modules assertion now that AuthorPage imports them

PR #7736 added a lint assertion that admin/src/components/SearchField.tsx
and admin/src/utils/sorting.ts must NOT exist, on the assumption they
were dead code after #7716. They aren't — the GDPR AuthorPage (#7667)
imports both. The previous commit restored the files; this commit flips
the assertion so the test:

  - verifies both files exist
  - verifies admin/src/pages/AuthorPage.tsx still imports them

If a future cleanup wants to delete these modules, the test now forces
the author to also delete or refactor the AuthorPage consumption first,
preventing a repeat of the merge-order accident that produced this bug.

Test plan:
  - cd src && pnpm exec vitest run tests/backend-new/specs/admin-i18n-source-lint.test.ts
    → 14 passed (14)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit to JohnMcLear/etherpad that referenced this pull request May 15, 2026
…#7666)

Takes over ether#7666 / closes ether#7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework ether#7716
and admin i18n fixes ether#7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).

Highlights:

- New parsed JSONC settings editor under
  admin/src/components/settings/ — FormView, ModeToggle,
  ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
  number, bool, null, env pill), and pure helpers (comments,
  envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
  that round-trip through the raw textarea (env-pill spec asserts
  this; docker-template spec protects against form-view degradation
  on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
  inlined at build time via vite (drops the runtime fs.allow
  widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
  /admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
  edits that preserve key order and trailing comments (stops at
  end-of-line so trailing-comment trains don't bleed into the next
  property).
- i18n keys added for form mode, parse error, env pill,
  default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
  raw round-trip, and form-mode regressions called out in ether#7666
  review (stable React keys from AST offsets, save-toast on server
  ack only, NumberInput draft sync, parse-error flash during
  initial load, .settings CSS conflict resolution, focus retention
  via rAF, IconButton type defaulting to 'button').

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit to JohnMcLear/etherpad that referenced this pull request May 15, 2026
…#7666)

Takes over ether#7666 / closes ether#7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework ether#7716
and admin i18n fixes ether#7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).

Highlights:

- New parsed JSONC settings editor under
  admin/src/components/settings/ — FormView, ModeToggle,
  ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
  number, bool, null, env pill), and pure helpers (comments,
  envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
  that round-trip through the raw textarea (env-pill spec asserts
  this; docker-template spec protects against form-view degradation
  on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
  inlined at build time via vite (drops the runtime fs.allow
  widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
  /admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
  edits that preserve key order and trailing comments (stops at
  end-of-line so trailing-comment trains don't bleed into the next
  property).
- i18n keys added for form mode, parse error, env pill,
  default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
  raw round-trip, and form-mode regressions called out in ether#7666
  review (stable React keys from AST offsets, save-toast on server
  ack only, NumberInput draft sync, parse-error flash during
  initial load, .settings CSS conflict resolution, focus retention
  via rAF, IconButton type defaulting to 'button').

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit to JohnMcLear/etherpad that referenced this pull request May 15, 2026
…#7666)

Takes over ether#7666 / closes ether#7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework ether#7716
and admin i18n fixes ether#7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).

Highlights:

- New parsed JSONC settings editor under
  admin/src/components/settings/ — FormView, ModeToggle,
  ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
  number, bool, null, env pill), and pure helpers (comments,
  envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
  that round-trip through the raw textarea (env-pill spec asserts
  this; docker-template spec protects against form-view degradation
  on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
  inlined at build time via vite (drops the runtime fs.allow
  widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
  /admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
  edits that preserve key order and trailing comments (stops at
  end-of-line so trailing-comment trains don't bleed into the next
  property).
- i18n keys added for form mode, parse error, env pill,
  default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
  raw round-trip, and form-mode regressions called out in ether#7666
  review (stable React keys from AST offsets, save-toast on server
  ack only, NumberInput draft sync, parse-error flash during
  initial load, .settings CSS conflict resolution, focus retention
  via rAF, IconButton type defaulting to 'button').

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JohnMcLear added a commit that referenced this pull request May 15, 2026
…7709)

* admin: parsed JSONC settings editor with form view (#7603, #7666)

Takes over #7666 / closes #7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework #7716
and admin i18n fixes #7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).

Highlights:

- New parsed JSONC settings editor under
  admin/src/components/settings/ — FormView, ModeToggle,
  ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
  number, bool, null, env pill), and pure helpers (comments,
  envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
  that round-trip through the raw textarea (env-pill spec asserts
  this; docker-template spec protects against form-view degradation
  on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
  inlined at build time via vite (drops the runtime fs.allow
  widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
  /admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
  edits that preserve key order and trailing comments (stops at
  end-of-line so trailing-comment trains don't bleed into the next
  property).
- i18n keys added for form mode, parse error, env pill,
  default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
  raw round-trip, and form-mode regressions called out in #7666
  review (stable React keys from AST offsets, save-toast on server
  ack only, NumberInput draft sync, parse-error flash during
  initial load, .settings CSS conflict resolution, focus retention
  via rAF, IconButton type defaulting to 'button').

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): stabilise React keys to prevent focus loss in settings editor

Switch React keys in JsoncNode and FormView from byte offsets to stable
JSON paths (`getNodePath(...).join('.')`). Byte offsets shift on every
keystroke because the edit changes the surrounding character count,
which forces React to remount inputs and lose focus mid-typing.

- Object children key on the property path.
- Array elements key on their JSON path index.
- Add a Playwright regression test pinning focus stability for array
  element edits.

Co-authored-by: John McLear <john@mclear.co.uk>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(admin): stop trailing /* */ comments from bleeding into next key's label (#7740)

In the parsed settings form view, each key's row was rendering its label
as the previous keys' source lines concatenated together. Root cause:
findLeading() in admin/src/components/settings/comments.ts treated any
line ending in `*/` as a comment continuation, so a JSON line like
  "altF9": true, /* focus on the File Menu and/or editbar */
was absorbed into the next sibling's leading comment block, and then
each subsequent key picked up an even longer accumulation.

- Tighten findLeading's isComment check to only match structural comment
  lines (`//`, `/*`, or a `*`-prefixed continuation/close), so JSON code
  with a trailing block comment no longer matches.
- Surface leading and trailing comments separately from the template
  map. Leaf rows with only a trailing same-line comment now render the
  humanized key as the row label and the comment as the help text below
  the control, matching settings.json.template's convention (and #7740's
  recommendation that "helper text should be below").
- Add unit tests pinning the regression and the JSDoc/`//` leading
  styles, plus a Playwright spec that asserts altC's row carries a
  clean label and the "focus on the Chat window" help text.

Closes #7740.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Hardcoded Non English default Translations on /admin page

1 participant