etherpad-lite/admin
John McLear ff7c4d5310
fix(admin): replace hardcoded German strings with i18n keys (#7735) (#7736)
* fix(admin): replace ~50 hardcoded German strings with i18n keys (#7735)

PR #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 #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>

* fix(admin): reuse existing ep_admin_pads:* keys instead of duplicating

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>

* fix(admin): address Qodo finding + restore #7716 regressions

Qodo flagged a reliability bug in PadPage on PR #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 #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 #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>

* docs(agents): add mandatory i18n + a11y guardrails

PR #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>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 10:09:44 +01:00
..
public chore: use only rolldown for building (#7101) 2025-08-26 22:22:10 +02:00
scripts feat(admin): document admin endpoints in OpenAPI (#7693) (#7705) 2026-05-10 15:55:33 +08:00
src fix(admin): replace hardcoded German strings with i18n keys (#7735) (#7736) 2026-05-12 10:09:44 +01:00
.eslintrc.cjs Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
.gitignore Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
index.html Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
package.json chore: fixed admin design rework (#7716) 2026-05-10 18:01:07 +02:00
README.md feat(admin): document admin endpoints in OpenAPI (#7693) (#7705) 2026-05-10 15:55:33 +08:00
tsconfig.json feat(admin): document admin endpoints in OpenAPI (#7693) (#7705) 2026-05-10 15:55:33 +08:00
tsconfig.node.json Feat/admin react (#6211) 2024-03-09 23:07:09 +01:00
vite.config.ts fix(admin): restore i18n on /admin (issue #7586) (#7602) 2026-04-26 03:07:45 +01:00

Admin UI

Vite + React 19 single-page app served at /admin. Talks to the backend over socket.io for the existing settings / plugins / pads pages, and (when endpoints are added to the OpenAPI spec) over a typed REST client.

Scripts

Script What it does
pnpm dev gen:api + Vite dev server (expects backend on :9001).
pnpm gen:api Regenerates src/api/{schema.d.ts,version.ts} from the OpenAPI spec.
pnpm build gen:api + tsc + vite build.
pnpm build-copy Same, but writes into ../src/templates/admin.
pnpm test gen:api + smoke tests for the API client wiring.
pnpm lint ESLint.

Typed API client

The admin uses openapi-typescript to generate types from src/node/hooks/express/openapi.ts, openapi-fetch for typed requests, and openapi-react-query for TanStack Query bindings.

Generated files

admin/src/api/schema.d.ts and admin/src/api/version.ts are generated by gen:api and gitignored — never commit them. They are produced by:

pnpm --filter admin gen:api

admin/scripts/gen-api.mjs loads src/node/hooks/express/openapi.ts, calls generateDefinitionForVersion for the latest API version, pipes the JSON through openapi-typescript to produce schema.d.ts, and emits a runtime constant LATEST_API_VERSION (read from info.version in the spec) to version.ts so client.ts can build the right /api/<version>/ baseUrl.

gen:api runs as the first step of dev, build, build-copy, and test, so a fresh checkout produces the generated files automatically when any of those scripts is invoked. After modifying any of the following, the next pnpm <dev|build|test> will refresh the generated files; you can also run gen:api directly:

  • src/node/hooks/express/openapi.ts
  • src/node/handler/APIHandler.ts (changes to latestApiVersion)
  • the resource definitions referenced by openapi.ts

Using the client

import { $api } from './api/client';

const SettingsPanel = () => {
  const { data } = $api.useQuery('get', '/admin/settings'); // example
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
};

The admin endpoints are not yet present in the OpenAPI spec — this client is in place to support upcoming work (see issue #7638 follow-up). For now, it is exercised only by the smoke test.