dorfteich/apps/web/src/i18n/index.ts
Claude Opus 4.8 6d3db7db38
All checks were successful
CD / Build and push images (push) Successful in 3m17s
CI / Lint, typecheck, test (push) Successful in 2m34s
CI / Auth e2e pack (push) Successful in 3m27s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
Add Site-Admin quota override management UI (#58)
Site Admins tune quotas per user and per pond on the three-level ladder
(pond override → user override → instance default, data-model.md §Quotas).

- api `admin/`: a `QuotaAdminService` + Site-Admin-gated endpoints under
  `/admin/quotas` — look up a user (username/e-mail) or pond (slug), list every
  quota's override / instance default / effective value (resolved through the
  existing QuotaService, the single consumption path, so a change takes effect
  immediately) plus current usage, and set/clear a per-subject override. Every
  change is audit-logged. A pond's effective values resolve on its own override
  then its owner's, matching the consumption checks.
- web: the Admin area gains a 'Quotas' surface — the instance defaults move
  into a proper number-input form (was raw settings, #19), and a per-subject
  panel looks a user/pond up, shows the ladder with usage, flags subjects over
  their effective limit, and sets/clears overrides. New `quotas` i18n
  namespace (de+en).
- tests: `quota-admin.e2e.db.test.ts` (override → effective changes at once and
  QuotaService sees it; clear → falls back to the default; lookup; Site-Admin
  gating); a browser `admin-quotas` pack proving an override raised in the UI
  immediately lets a user create another shared pond (issue #22 consumption).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-10 00:14:28 +02:00

77 lines
2.9 KiB
TypeScript

import deAccess from '@dorfteich/shared/i18n/de/access.json';
import deAuth from '@dorfteich/shared/i18n/de/auth.json';
import deCommon from '@dorfteich/shared/i18n/de/common.json';
import deEditor from '@dorfteich/shared/i18n/de/editor.json';
import deErrors from '@dorfteich/shared/i18n/de/errors.json';
import deLabels from '@dorfteich/shared/i18n/de/labels.json';
import deLinks from '@dorfteich/shared/i18n/de/links.json';
import deMembers from '@dorfteich/shared/i18n/de/members.json';
import dePublic from '@dorfteich/shared/i18n/de/public.json';
import deQuotas from '@dorfteich/shared/i18n/de/quotas.json';
import deSearch from '@dorfteich/shared/i18n/de/search.json';
import deSettings from '@dorfteich/shared/i18n/de/settings.json';
import enAccess from '@dorfteich/shared/i18n/en/access.json';
import enAuth from '@dorfteich/shared/i18n/en/auth.json';
import enCommon from '@dorfteich/shared/i18n/en/common.json';
import enEditor from '@dorfteich/shared/i18n/en/editor.json';
import enErrors from '@dorfteich/shared/i18n/en/errors.json';
import enLabels from '@dorfteich/shared/i18n/en/labels.json';
import enLinks from '@dorfteich/shared/i18n/en/links.json';
import enMembers from '@dorfteich/shared/i18n/en/members.json';
import enPublic from '@dorfteich/shared/i18n/en/public.json';
import enQuotas from '@dorfteich/shared/i18n/en/quotas.json';
import enSearch from '@dorfteich/shared/i18n/en/search.json';
import enSettings from '@dorfteich/shared/i18n/en/settings.json';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
/**
* Translation resources are bundled (no async loading): the whole catalog
* is small, and the offline-capable editor (ADR 0003) must not depend on
* fetching language files. Detection: ?lng=… wins, then the browser
* language; the user-profile setting (issue #17) will be layered on top.
*/
void i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: {
common: enCommon,
errors: enErrors,
access: enAccess,
auth: enAuth,
settings: enSettings,
editor: enEditor,
labels: enLabels,
links: enLinks,
members: enMembers,
public: enPublic,
quotas: enQuotas,
search: enSearch,
},
de: {
common: deCommon,
errors: deErrors,
access: deAccess,
auth: deAuth,
settings: deSettings,
editor: deEditor,
labels: deLabels,
links: deLinks,
members: deMembers,
public: dePublic,
quotas: deQuotas,
search: deSearch,
},
},
defaultNS: 'common',
fallbackLng: 'en',
supportedLngs: ['de', 'en'],
interpolation: { escapeValue: false }, // React already escapes.
detection: { order: ['querystring', 'navigator'], lookupQuerystring: 'lng', caches: [] },
});
export default i18n;