Files
picpeak/frontend/i18next.config.ts
T
Paul Nothaft 413290af3e i18n: fail safe on empty strings, normalise German to Sie
D2 -- returnEmptyString: false. i18next defaults it to true, so an empty
translation was returned as valid and rendered as blank UI instead of falling
back to English. Verified safe first: zero empty-string values across all 8
locales, no addResourceBundle or runtime resource injection, no public/locales
for the HTTP backend, and the three t(key, '') call sites resolve against key
families fully populated in en and de.

D3 -- German formality normalised to Sie throughout, 101 strings. There is no
deliberate du island: Sie outnumbered du roughly 6:1 (~390 vs 65 addressed
strings), every namespace with more than ten addressed strings was
Sie-dominant, and the guest gallery plus all public/billing surfaces were
already 100% Sie. Even customer.*, the reported offender, was internally mixed
rather than consistently du. Two detection passes: du-pronouns (now zero) and
du-imperatives without a pronoun (Klicke…, Aktiviere…, Wähle…). Placeholders
verified mechanically unchanged. Left alone: ten 1st-person progress labels
(Lade Benutzer…, Prüfe…, Teste Verbindung…) -- those are label style, not
address, and normalising two of ten would have made it worse.

C7 -- removeUnusedKeys stays false, but the comment now carries measured
evidence instead of an estimate. The honest attempt was made: 61 preserve
globs derived mechanically from all 82 dynamic key templates in src (far more
than the 5 families previously named) plus 17 constant-table prefixes cut
removals from 422 to 158. Two things still block it. 47 of the remainder are
the base form of a plural key that src does pass to t(); i18next tries the
_other suffix first so nothing visibly breaks, but covering them needs a
literal pattern per key and forgetting one silently deletes a live key --
exactly the failure the flag prevents. And pruning is not idempotent: run for
real, extract had to run three times before --ci --dry-run came back clean,
each pass uncovering another removal, so i18n:ci would fail on a correct tree
until someone ran extract enough times.

Also adds the three settings.analytics keys that 9251745a referenced in
AnalyticsTab without adding (proxiedNotice, proxiedNoticeText,
customOnlyCspWarningText) -- en from the source defaults, de translated.

Refs testplan REPORT.md C7, D2, D3.
2026-09-02 09:43:10 +02:00

60 lines
2.9 KiB
TypeScript

import { defineConfig } from 'i18next-cli';
import { typescriptPlugin } from "./scripts/i18nextExtractionHelper";
export default defineConfig({
// Only the locales that are kept at full key parity are managed by the extractor.
// nl/pt/ru/fr/sl/es are deliberately partial and rely on `fallbackLng: 'en'`; letting
// the extractor own them would fill each file with ~2700 empty-string values, and
// i18next's default `returnEmptyString: true` renders those as blank UI instead of
// falling back to English.
locales: ['en', 'de'],
extract: {
input: ['src/**/*.{ts,tsx,js,jsx}'],
// `glob` (used by i18next-cli) ignores `!`-prefixed entries inside `input`,
// so exclusions have to live here or they are silently no-ops.
ignore: [
'src/**/*.{test,spec}.{ts,tsx,js,jsx}',
'src/**/__tests__/**',
'src/**/*.d.ts',
],
output: 'src/i18n/locales/{{language}}.json',
defaultNS: false,
primaryLanguage: 'en',
// Pruning is unsafe in this codebase. A large share of keys is never visible to the
// AST extractor because it is built at runtime — 82 distinct dynamic key templates in
// src, e.g. admin.activities, admin.notificationMessages, projects.status,
// accounting.expenseStatus, crmSettings — or held in constant tables the extractor does
// not resolve (AdminSidebar `nameKey`, CrmDevelopmentPage `titleKey`/`descKey`).
// Turned on as-is, it deletes 422 keys per locale, 203 of which src references.
//
// A `preservePatterns` rescue was attempted and measured: 61 globs derived
// mechanically from every dynamic template in src, plus 17 for the constant-table
// prefixes (78 patterns). That still leaves two unfixable problems:
//
// 1. 47 of the remaining 158 removals are the base form of a plural key
// (`upload.failures.title` next to `_one`/`_other`). src passes exactly those
// strings to t(), so they are referenced by any honest definition. i18next happens
// to resolve them via the `_other` suffix first, so nothing breaks today — but
// covering them needs 47 literal patterns and one more on every future {{count}}
// key, where forgetting one silently deletes a live key. That is the exact failure
// mode this flag is supposed to prevent.
// 2. Pruning is not idempotent. `extract` had to be run three times in a row before
// `extract --ci --dry-run` came back clean — each pass uncovered one further
// removal (`businessProfile.title`, then `cssTemplates.title`). i18n:ci would
// therefore fail on a correct tree until someone happened to run extract enough
// times. With pruning off the extractor settles in a single pass.
//
// Key removal stays a manual decision.
removeUnusedKeys: false,
preserveContextVariants: true,
indentation: 2,
sort: false,
},
plugins: [typescriptPlugin(["./src/App.tsx"]) ]
});