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