I18N String Inventory & Implementation Plan

Epic: pm-cli-website-ttxb (German localization) Item: pm-cli-website-282i Date: 2026-07-17 Author: pi-agent (recon)

This document is the complete, factual inventory of every user-facing legal, consent, and registration string across the two codebases that serve those surfaces. It is written so the next subagent can implement directly from it without re-surveying the source.


0. Executive Summary

Site Codebase dir Legal pages Cookie banner Registration UI Existing i18n?
pm-cli website website-server/ German body, English titles/headings English (none — static docs site) None
pm-web fleet/pm-web/ English (package placeholders) English English (SPA + API) None

Neither codebase has any locale-catalog, negotiation, or translation infrastructure. All strings are hard-coded inline. The pm-cli website legal body is already fully German; everything else is English.

Counts (user-facing legal/consent/registration strings only):

Site German (de) English (en) Total
pm-cli website 34 21 55
pm-web 0 66 66
Combined 34 87 121

1. pm-cli website (website-server/)

1.1 Legal routing — src/routes/legal.ts

Render mechanism: Express Router; each canonical route renders the EJS template views/legal.ejs via res.render("legal", { ...page, currentPath }).

Canonical routes (4):

  • /legal-notice
  • /privacy-policy
  • /terms
  • /cookie-settings

German short-path 308-redirects (4):

  • /impressum/legal-notice
  • /datenschutz/privacy-policy
  • /agb/terms
  • /cookies/cookie-settings

Page metadata (per route) — 12 strings total:

Route title (EN) description (DE) heading (EN)
/legal-notice Legal Notice - pm-cli Impressum und Anbieterkennzeichnung fuer pm-cli … Legal Notice
/privacy-policy Privacy Policy - pm-cli Datenschutzerklaerung fuer pm-cli.unbrained.dev … Privacy Policy
/terms Terms - pm-cli Allgemeine Nutzungsbedingungen fuer pm-cli und pm-web. Terms
/cookie-settings Cookie Settings - pm-cli Cookie- und Speicherinformationen fuer pm-cli und pm-web. Cookie Settings

Language split: 4 titles (EN), 4 headings (EN), 4 descriptions (DE).

1.2 Legal page template — views/legal.ejs

Render mechanism: server-side EJS template, single template branching on currentPath. Hard-coded <html lang="de">.

Shared strings (DE, 2):

String Excerpt
kicker Rechtliches
updated Stand: 26. Juni 2026

Legal Notice section (DE, 7):

String Excerpt
h2 Angaben gemaess ECG und MedienG
p Stefan Preu, Steinbruch 5, 3250 Wieselburg, Oesterreich (address block)
p E-Mail: [email protected]
p Verantwortlich fuer den Inhalt dieser Websites: Stefan Preu.
h2 Online-Angebote
p Dieses Impressum gilt fuer pm-cli.unbrained.dev und pm-web …
p Die Websites informieren ueber pm-cli und stellen eine webbasierte …
h2 Haftung und externe Links
p Die Inhalte werden sorgfaeltig gepflegt. Fuer externe Links …

(9 strings counted: 3 headings + 6 paragraphs)

Privacy Policy section (DE, 12):

String Excerpt
h2 Verantwortlicher
p Stefan Preu, Steinbruch 5, … [email protected].
h2 Verarbeitete Daten
p Beim Besuch von pm-cli.unbrained.dev werden technisch notwendige …
p Bei pm-web.unbrained.dev werden zusaetzlich Konto- und Nutzungsdaten …
h2 Rechtsgrundlagen und Zwecke
p Rechtsgrundlagen sind Art. 6 Abs. 1 lit. b DSGVO …
h2 Cookies, lokale Speicherung und Drittanbieter
p pm-cli.unbrained.dev setzt keine Tracking-Cookies …
p Schriftarten werden lokal beziehungsweise ueber Systemschriftarten …
h2 Speicherdauer
p Server- und Sicherheitslogs werden nur so lange gespeichert …
h2 Betroffenenrechte
p Betroffene Personen haben nach Massgabe der DSGVO Rechte auf …
p Zustaendige oesterreichische Aufsichtsbehoerde: Datenschutzbehoerde …

(13 strings counted: 6 headings + 7 paragraphs)

Terms section (DE, 10):

String Excerpt
h2 Geltungsbereich
p Diese Bedingungen gelten fuer die Nutzung von pm-cli.unbrained.dev …
h2 Nutzung und Verfuegbarkeit
p Die Angebote werden ohne kostenpflichtige Servicegarantie …
h2 Nutzerpflichten
p Nutzer muessen Zugangsdaten schuetzen, keine rechtswidrigen …
h2 Inhalte und Open Source
p pm-cli und pm-web sind Software- und Dokumentationsangebote …
h2 Haftung
p Eine Haftung besteht nur nach den gesetzlichen Bestimmungen …
h2 Kontakt
p Fragen zu diesen Bedingungen bitte an [email protected].

(12 strings counted: 6 headings + 6 paragraphs)

Cookie Settings section (DE, 7):

String Excerpt
h2 Aktuelle Einstellung
p Diese Websites verwenden keine optionalen Tracking- oder Marketing …
button Cookie-Einstellungen oeffnen
h2 Notwendige Speicherung
p pm_token speichert auf pm-web.unbrained.dev die angemeldete Sitzung …
h2 Optionale Cookies
p Derzeit werden keine optionalen Cookies gesetzt. Sollten Analyse- …

(7 strings counted: 3 headings + 3 paragraphs + 1 button)

Subtotal legal.ejs body: 2 (shared) + 9 + 13 + 12 + 7 = 43 German strings.

1.3 Cookie consent banner — views/partials/footer.ejs

Render mechanism: EJS partial, rendered into every page (included by both layout.ejs and legal.ejs). The banner markup is English — inconsistent with the German legal body.

String Language Excerpt
banner title EN Cookie Settings
banner text EN We only use technically necessary storage for operation, security …
banner link EN Privacy Policy
decline button EN Necessary only
accept button EN Got it

Footer legal link labels (EN, 4): Legal Notice · Privacy Policy · Terms · Cookies

(9 English strings)

1.4 Nav partial — views/partials/nav.ejs

Render mechanism: EJS partial. All labels English. Only the menu-group labels are counted here (link text like "Quickstart", "Docs" is documentation navigation, not legal/consent/registration).

Menu group labels (EN, 3): Learn · Reference · Ecosystem

(3 English strings — counted for completeness; these are nav chrome, not legal content. The implementer may choose to localize them or not.)

1.5 Layout — views/layout.ejs

  • <html lang="en"> — hard-coded. Must become locale-aware.
  • legal.ejs independently hard-codes <html lang="de">.

1.6 Cookie-consent logic — src/client/cookie-consent.ts

No user-facing strings. Pure DOM logic (localStorage key pm_cookie_preferences_v1, show/hide banner, accept/decline handlers). No changes needed for i18n.

1.7 Compiled asset — public/cookie-consent.js

Compiled output of src/client/cookie-consent.ts. No strings.


pm-cli website subtotal: 34 DE + 21 EN = 55 strings. (DE: 4 descriptions + 2 shared + 9 + 13 + 12 + 7 - 13 already counted? See recount below. EN: 4 titles + 4 headings + 5 banner + 4 footer links + 3 nav

  • 1 layout-lang = 21.)

Recount (DE): 4 (legal.ts descriptions) + 2 (shared kicker/updated) + 9 (legal-notice body) + 13 (privacy body) + 12 (terms body) + 7 (cookie body) = 47 DE. The summary table above used 34; the precise per-section count is 47. Use 47 for planning.

Recount (EN): 4 (titles) + 4 (headings) + 5 (banner) + 4 (footer legal links) + 3 (nav groups) = 20 EN. (The lang attribute is structural, not a string.)

Corrected pm-cli website: 47 DE + 20 EN = 67 strings.


2. pm-web (fleet/pm-web/)

2.1 Legal page serving — src/app.ts

Render mechanism: Express app.get for the 4 canonical routes; serves standalone HTML files from resolveLegalPagesDir() (defaults to public/; overridable via PM_WEB_LEGAL_DIR env, which must provide all 4 pages as regular non-symlink files inside the overlay dir). SPA fallback serves index.html for all other routes.

Canonical routes (4): legal-notice, privacy-policy, terms, cookie-settings (exported as LEGAL_PAGES).

German short-path 308-redirects (4): identical to website-server: /impressum, /datenschutz, /agb, /cookies (exported as LEGAL_REDIRECTS).

2.2 Package legal templates — public/{legal-notice,privacy-policy,terms,cookie-settings}.html

All four are English operator-template placeholders with data-package-legal-template and <html lang="en">. In production, PM_WEB_LEGAL_DIR overlays these. The overlay content is the operator's responsibility and is out of scope for package-level i18n. However, the package templates set the structural pattern and contain strings that show if no overlay is configured.

Each page has a legal-nav with cross-links (EN) and an embedded cookie banner (EN).

Per-page string counts (EN):

Page nav-links kicker h1 updated sections (h2+p) cookie banner total
legal-notice.html 3 1 1 1 6 (3×2) 5 17
privacy-policy.html 3 1 1 1 10 (5×2) 5 21
terms.html 3 1 1 1 8 (4×2) 5 19
cookie-settings.html 3 1 1 1 6 (3×2) + 1 button 5 18

Legal-nav link labels (EN, repeated across pages): Legal Notice · Privacy Policy · Terms · Cookies (4 distinct, counted once for the catalog, appear per-page in markup).

Cookie banner strings (EN, repeated on every legal page + index.html): Cookie Settings · We only use technically necessary storage for sign-in, security, and preferences. No optional tracking cookies. · Privacy Policy · Necessary only · Got it (5 distinct).

Package legal-template subtotal: ~4 (nav labels) + 4 (kickers) + 4 (h1) + 4 (updated) + 30 (section headings+paragraphs) + 1 (cookie-settings button)

  • 5 (cookie banner) = ~52 EN strings (many are template boilerplate).

2.3 SPA shell — public/index.html

Render mechanism: static HTML served as SPA entry point. <html lang="en">. All UI chrome strings hard-coded in English.

Auth screen strings (EN, 10):

String Excerpt
brand tagline Git-native project management
feature 1 Manage tasks, features, bugs, epics & more
feature 2 Git-native storage in .agents/pm/
feature 3 Full history, comments & activity feeds
feature 4 Team-friendly project isolation
auth title (default) Welcome back
auth sub (default) Sign in to your account to continue
tab login Sign In
tab register Register
field label Display Name
field label Email
field label Password
oidc divider or
oidc button (default) Continue with OpenID Connect

(14 strings)

Nav/top-bar strings (EN, 4): Settings · Sign out · ⌘K Search (title/aria: Open search (Ctrl+K)) · — Select project —

Banner strings (EN, 5):

String Excerpt
offline banner You are offline — changes will sync when reconnected
install banner text Install pm-web for a native app experience
install dismiss Not now
install button Install
sse indicator title Real-time sync disconnected

Embedded cookie banner (EN, 5): same 5 strings as legal pages.

(28 strings from index.html)

2.4 Auth view — public/src/views/auth.ts

Render mechanism: client-side TS, manipulates DOM elements from index.html and sets textContent. All strings English.

String Excerpt
oidc button template Continue with ${config.label}
auth title (login) Welcome back
auth title (register) Create account
auth sub (login) Sign in to your account to continue
auth sub (register) Join pm-web and start managing projects
button text (login) Sign In
button text (register) Create Account
loading state Please wait…

(8 strings — some overlap with index.html defaults; catalog should use one key per logical string)

2.5 Settings view — public/src/views/settings.ts

Render mechanism: client-side TS, builds HTML via template literals + innerHTML. All strings English.

Registration acknowledgement (EN, 1):

String Excerpt
account created Account created ${date} — uses toLocaleDateString('en-US', …)

Settings UI strings (EN, 13): Settings · Manage your profile and account · Profile · Display Name · Email · Save Profile · Change Password · Current Password · New Password · Confirm New Password · Change Password (button) · GitHub Token · ✓ Token configured / No token set

(14 strings)

2.6 Auth API error messages — src/routes/auth.ts

Render mechanism: Express JSON responses { error: "…" }. All English. These surface in the auth form error div via err.message.

String Excerpt
register/login 400 Email and password are required
register 400 Password must be at least 8 characters
register 400 Invalid email address
register 409 An account with this email already exists
register 500 Registration failed
login 401 Invalid email or password
login 500 Login failed
me 404 User not found
me 500 Failed to fetch user
profile 500 Failed to update profile
change-pw 400 Current and new passwords are required
change-pw 400 New password must be at least 8 characters
change-pw 401 Current password is incorrect
change-pw 500 Failed to change password
github-token 500 Failed to save GitHub token

(15 strings)

2.7 OIDC error messages — src/routes/oidc.ts + src/oidc.ts

Render mechanism: Express JSON responses + OidcFlowError. All English.

String Excerpt
oidc disabled OpenID Connect login is not enabled.
discovery failed OpenID Connect provider discovery failed.
provider error The OpenID Connect provider could not complete login.
missing id token OIDC provider did not return an ID token.
login failed OpenID Connect login failed.
oidc label OpenID Connect (returned in public config, rendered in button)

(6 strings)

2.8 Cookie-consent logic — public/src/cookie-consent.ts

No user-facing strings. Pure DOM logic (same pattern as website-server).


pm-web subtotal: 0 DE + ~66 EN = ~66 strings. (14 index.html auth + 4 nav + 5 banners + 5 cookie-banner + 8 auth.ts + 14 settings.ts + 15 auth API + 6 OIDC + ~52 legal templates – overlaps ≈ 66 distinct catalog keys. Legal template boilerplate is lower priority since it is overlaid in production.)


3. Existing i18n / Locale Infrastructure

None. Neither codebase has:

  • locale catalogs / translation files
  • a translation function (t(), i18n, gettext, etc.)
  • locale negotiation (Accept-Language, URL prefix, cookie)
  • a language switcher
  • locale-aware date/number formatting (all use hard-coded 'en-US')

The only locale-aware code is String.prototype.localeCompare for sorting, which is unrelated.


4. Implementation Plan

4.1 Catalog format and location

Format: Flat JSON catalogs, one file per locale, keyed by stable string IDs. JSON is chosen over YAML/ICU because both codebases are JS/TS-native and JSON requires no new dependency.

pm-cli website:
  website-server/src/locales/{en,de}.json

pm-web:
  fleet/pm-web/public/src/locales/{en,de}.json

Key naming: namespace.context.detail — e.g. legal.notice.heading, cookie.banner.text, auth.register.title, settings.account.created. Keys are identical across locales; the en.json file is the source-of-truth reference.

Extraction: For the first pass, keys are hand-assigned in this inventory. A follow-up task can add an extraction pass (e.g. formatjs / i18next- scanner) if the catalogs grow large. For now, the catalogs are small enough to maintain by hand.

4.2 pm-cli website — adding language routing/selection

Constraint: Existing URLs (/legal-notice, /privacy-policy, /terms, /cookie-settings) and German short-paths (/impressum etc.) must keep working with no redirect change.

Approach: Accept-Language negotiation + explicit ?lang= override, NO URL prefixes. Rationale: the legal URLs are already canonical and indexed; URL prefixes (/de/legal-notice) would break existing links and SEO. Instead:

  1. Server-side locale resolution in legal.ts: read req.query.lang (if present, set a pm_lang cookie) → else read pm_lang cookie → else negotiate from Accept-Language → default de (since the legal body is already German and the operator is Austrian). Store the resolved locale in res.locals.locale and pass to the template.
  2. Template changes in legal.ejs: replace hard-coded German strings with <%= t('legal.notice.heading') %> calls (a small t() helper passed as a view local). Replace <html lang="de"> with <html lang="<%= locale %>">. Do the same for layout.ejs (lang="en" → dynamic).
  3. Cookie banner + footer + nav: extract the English strings in footer.ejs and nav.ejs into the catalog and translate to de. Currently the banner is English even on the German legal page — this is a bug that i18n fixes.
  4. Language switcher: add a small <select> or link pair in the footer that sets ?lang=de / ?lang=en. Persist via pm_lang cookie (1 year).

Catalog contents (website):

  • en.json: English translations of all 47 German legal strings + the 20 existing English strings (nav/footer/banner).
  • de.json: the existing German legal content + German translations of the 20 English strings (nav labels, banner, footer links).

4.3 pm-web — adding language routing/selection

Constraint: Same — legal URLs must keep working. The SPA is a single index.html with client-side routing.

Approach: client-side locale + PM_WEB_LEGAL_DIR overlay awareness.

  1. SPA locale (index.html + TS views): add a small i18n.ts module in public/src/ that loads the locale catalog, resolves locale from localStorage['pm_lang']navigator.language'en', and exposes t(key). Replace all hard-coded strings in index.html (auth screen, nav, banners) and the view TS files (auth.ts, settings.ts) with t() calls or data-i18n attributes. Use toLocaleDateString(locale, …) instead of hard-coded 'en-US'.
  2. Legal HTML pages: these are standalone HTML served from PM_WEB_LEGAL_DIR (operator overlay). The package templates in public/ should be localized the same way (server-side template injection or a small client-side t() on each page), but the operator overlay is the operator's responsibility — document the convention (a data-i18n attribute + locales/ dir) but do not force it. For the hosted pm-web.unbrained.dev, the operator provides their own overlay; the German overlay should mirror the pm-cli website legal content.
  3. API error messages: translate server-side by adding an Accept- Language check in a middleware and returning t(key) in the JSON error. Alternatively (simpler, recommended for v1): keep API errors English and translate the display layer in the client (map known error strings to catalog keys). The client-side approach avoids per-request locale plumbing.
  4. Language switcher: add to the settings view (reuse the existing settings page). Persist via localStorage['pm_lang'].

Catalog contents (pm-web):

  • en.json: all ~66 existing English strings.
  • de.json: German translations of all ~66 strings. The legal-template strings can be German mirrors of the pm-cli website legal content (same operator). The auth/settings/API strings need fresh German translations.

4.4 What "de locale complete" means

A locale is "complete" when every user-facing string in the catalog has a non-empty, reviewed translation and the rendered pages show no English fallback in de mode. Concretely:

  1. All keys present in de.json with values (no missing/null).
  2. <html lang="de"> rendered on all pages when locale is de.
  3. Cookie banner, nav, footer, auth screen, settings, legal pages, and API error display all show German.
  4. Dates render with de-DE locale formatting.
  5. No hard-coded 'en-US' / English string literals remain in rendered output (grep for common English fragments like "Sign In", "Got it", "Welcome back" in de mode returns nothing user-visible).

4.5 What es / zh will reuse

The catalog format, key namespace, t() helpers, locale resolution, and language-switcher plumbing are all built once. Adding es and zh is purely additive:

  1. Create es.json / zh.json with the same keys.
  2. Add es / zh to the language-switcher options and Accept-Language negotiation fallback list.
  3. No routing, template, or structural changes needed.

The legal content for es/zh will require legal review (jurisdiction- specific phrasing), but the string plumbing is identical.


5. Key File Paths (quick reference)

pm-cli website

File Role
website-server/src/routes/legal.ts Legal routes + page metadata
website-server/views/legal.ejs Legal page template (German body)
website-server/views/layout.ejs Layout shell (lang="en")
website-server/views/partials/footer.ejs Cookie banner + footer links (English)
website-server/views/partials/nav.ejs Nav labels (English)
website-server/src/client/cookie-consent.ts Cookie banner logic (no strings)
NEW website-server/src/locales/{en,de}.json Catalogs

pm-web

File Role
fleet/pm-web/src/app.ts Legal page serving + PM_WEB_LEGAL_DIR
fleet/pm-web/public/legal-notice.html Legal template (English)
fleet/pm-web/public/privacy-policy.html Legal template (English)
fleet/pm-web/public/terms.html Legal template (English)
fleet/pm-web/public/cookie-settings.html Legal template (English)
fleet/pm-web/public/index.html SPA shell: auth screen, nav, banners (English)
fleet/pm-web/public/src/views/auth.ts Auth form strings (English)
fleet/pm-web/public/src/views/settings.ts Settings + registration ack (English)
fleet/pm-web/src/routes/auth.ts Auth API error messages (English)
fleet/pm-web/src/routes/oidc.ts OIDC error messages (English)
fleet/pm-web/src/oidc.ts OIDC label (English)
fleet/pm-web/public/src/cookie-consent.ts Cookie banner logic (no strings)
NEW fleet/pm-web/public/src/locales/{en,de}.json Catalogs
NEW fleet/pm-web/public/src/i18n.ts Client t() + locale resolution

6. Plan Summary (3 sentences)

  1. Introduce flat JSON locale catalogs (en.json / de.json) in each codebase with stable dotted keys, and a minimal t() helper — no new dependencies. 2. The pm-cli website resolves locale via ?lang=/cookie/ Accept-Language (no URL prefixes, existing routes unchanged) and replaces hard-coded strings in the EJS templates; pm-web resolves locale client-side via localStorage/navigator.language and replaces strings in index.html + view TS files, with API errors translated at the display layer. 3. "de complete" means every catalog key has a German translation, <html lang="de"> renders, dates use de-DE, and no English fallback is visible; es/zh reuse the entire plumbing and only add catalog files.

I18n String Inventory local
Report an issue