From 8b9fbd2e1cdd543b7cdfeca187ac8f60a2cb0e7f Mon Sep 17 00:00:00 2001 From: Till JS Date: Sat, 25 Apr 2026 11:33:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(scripts):=20validate:i18n-hardcoded=20?= =?UTF-8?q?=E2=80=94=20ratcheting=20baseline=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stoppt das Wachsen des 1877-String-Backlogs hardgecodeter deutscher User-facing Strings in .svelte Files. Per-file Count vs. committed Baseline; Datei darf NIE über ihrer Baseline liegen, neue Files müssen mit 0 Verstößen starten. - Erkennt: placeholder/title/aria-label/label/alt mit Umlauten, Text-Content `>Großbuchstabe…<` (ohne Interpolation). - Aktuelle Baseline: 1877 Verstöße in 428 Files; jeder Fix ratchet't den erlaubten Wert nach unten. - Lokales Update nach gewolltem Wachstum: `pnpm run validate:i18n-hardcoded -- --update`. - In validate:all + CI verdrahtet. - Drift-Test bestätigt: ein zusätzlicher umlaut-Placeholder lässt die Datei "2 (was 1, +1)" failen. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 3 +- scripts/i18n-hardcoded-baseline.json | 430 ++++++++++++++++++++++ scripts/validate-no-hardcoded-strings.mjs | 125 +++++++ 3 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 scripts/i18n-hardcoded-baseline.json create mode 100644 scripts/validate-no-hardcoded-strings.mjs diff --git a/package.json b/package.json index ce0422e78..cf43888a2 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "validate:theme-utilities": "node scripts/validate-theme-utilities.mjs", "validate:theme-parity": "node scripts/validate-theme-parity.mjs", "validate:i18n-parity": "node scripts/validate-i18n-parity.mjs", - "validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run validate:theme-variables && pnpm run validate:theme-utilities && pnpm run validate:theme-parity && pnpm run validate:i18n-parity && pnpm run check:crypto && pnpm run audit:encrypted-tools", + "validate:i18n-hardcoded": "node scripts/validate-no-hardcoded-strings.mjs", + "validate:all": "pnpm run validate:turbo && pnpm run validate:pg-schema && pnpm run validate:theme-variables && pnpm run validate:theme-utilities && pnpm run validate:theme-parity && pnpm run validate:i18n-parity && pnpm run validate:i18n-hardcoded && pnpm run check:crypto && pnpm run audit:encrypted-tools", "check:crypto": "node scripts/audit-crypto-registry.mjs", "check:crypto:seed": "node scripts/audit-crypto-registry.mjs --seed", "audit:encrypted-tools": "bun run scripts/audit-encrypted-tools.ts", diff --git a/scripts/i18n-hardcoded-baseline.json b/scripts/i18n-hardcoded-baseline.json new file mode 100644 index 000000000..ae300bf01 --- /dev/null +++ b/scripts/i18n-hardcoded-baseline.json @@ -0,0 +1,430 @@ +{ + "apps/mana/apps/web/src/lib/components/admin/ProjectDataCard.svelte": 2, + "apps/mana/apps/web/src/lib/components/admin/QuickLinks.svelte": 1, + "apps/mana/apps/web/src/lib/components/admin/UserTable.svelte": 2, + "apps/mana/apps/web/src/lib/components/ai/AiDebugBlock.svelte": 1, + "apps/mana/apps/web/src/lib/components/ai/MissionGrantDialog.svelte": 2, + "apps/mana/apps/web/src/lib/components/ai/MissionInputPicker.svelte": 2, + "apps/mana/apps/web/src/lib/components/dashboard/DashboardGrid.svelte": 1, + "apps/mana/apps/web/src/lib/components/dashboard/TilePanel.svelte": 1, + "apps/mana/apps/web/src/lib/components/dashboard/widgets/CardsProgressWidget.svelte": 4, + "apps/mana/apps/web/src/lib/components/dashboard/widgets/MusicLibraryWidget.svelte": 1, + "apps/mana/apps/web/src/lib/components/DetailViewShell.svelte": 1, + "apps/mana/apps/web/src/lib/components/KeyboardShortcutsModal.svelte": 1, + "apps/mana/apps/web/src/lib/components/landing/LandingEditor.svelte": 16, + "apps/mana/apps/web/src/lib/components/layout/SpaceCreateDialog.svelte": 4, + "apps/mana/apps/web/src/lib/components/layout/SpaceSwitcher.svelte": 2, + "apps/mana/apps/web/src/lib/components/links/LinkedItems.svelte": 1, + "apps/mana/apps/web/src/lib/components/my-data/DeleteConfirmationModal.svelte": 9, + "apps/mana/apps/web/src/lib/components/my-data/ExportImportPanel.svelte": 3, + "apps/mana/apps/web/src/lib/components/my-data/QRExportModal.svelte": 5, + "apps/mana/apps/web/src/lib/components/observatory/SeenplatteScene.svelte": 1, + "apps/mana/apps/web/src/lib/components/observatory/ui/DetailPanel.svelte": 1, + "apps/mana/apps/web/src/lib/components/observatory/ui/LakeCard.svelte": 2, + "apps/mana/apps/web/src/lib/components/observatory/ui/RiverCard.svelte": 2, + "apps/mana/apps/web/src/lib/components/observatory/ui/TrendsChart.svelte": 2, + "apps/mana/apps/web/src/lib/components/page-carousel/PageCarousel.svelte": 1, + "apps/mana/apps/web/src/lib/components/profile/ChangePasswordModal.svelte": 2, + "apps/mana/apps/web/src/lib/components/profile/DeleteAccountModal.svelte": 5, + "apps/mana/apps/web/src/lib/components/profile/EditProfileModal.svelte": 3, + "apps/mana/apps/web/src/lib/components/settings/AiSettings.svelte": 1, + "apps/mana/apps/web/src/lib/components/settings/ByokKeysManager.svelte": 9, + "apps/mana/apps/web/src/lib/components/settings/sections/GeneralSection.svelte": 13, + "apps/mana/apps/web/src/lib/components/settings/sections/MyDataSection.svelte": 16, + "apps/mana/apps/web/src/lib/components/settings/sections/SyncSection.svelte": 6, + "apps/mana/apps/web/src/lib/components/settings/sections/TagPresetsSection.svelte": 3, + "apps/mana/apps/web/src/lib/components/settings/sections/VaultSection.svelte": 10, + "apps/mana/apps/web/src/lib/components/shell/ModuleShell.svelte": 3, + "apps/mana/apps/web/src/lib/components/SuggestionToast.svelte": 1, + "apps/mana/apps/web/src/lib/components/voice/VoiceCaptureBar.svelte": 1, + "apps/mana/apps/web/src/lib/components/wallpaper/WallpaperPicker.svelte": 7, + "apps/mana/apps/web/src/lib/components/workbench/AppPage.svelte": 6, + "apps/mana/apps/web/src/lib/components/workbench/AppPagePicker.svelte": 1, + "apps/mana/apps/web/src/lib/components/workbench/SceneAppBar.svelte": 1, + "apps/mana/apps/web/src/lib/components/workbench/scenes/BindAgentDialog.svelte": 5, + "apps/mana/apps/web/src/lib/components/workbench/ScopeEmptyState.svelte": 1, + "apps/mana/apps/web/src/lib/modules/admin/tabs/OverviewTab.svelte": 3, + "apps/mana/apps/web/src/lib/modules/admin/tabs/SystemTab.svelte": 8, + "apps/mana/apps/web/src/lib/modules/admin/tabs/UserDataTab.svelte": 9, + "apps/mana/apps/web/src/lib/modules/admin/tabs/UsersTab.svelte": 3, + "apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte": 23, + "apps/mana/apps/web/src/lib/modules/ai-health/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/ai-insights/ListView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte": 22, + "apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/api-keys/ListView.svelte": 11, + "apps/mana/apps/web/src/lib/modules/articles/components/AddUrlForm.svelte": 4, + "apps/mana/apps/web/src/lib/modules/articles/components/HighlightMenu.svelte": 5, + "apps/mana/apps/web/src/lib/modules/articles/components/HomeSectionSources.svelte": 1, + "apps/mana/apps/web/src/lib/modules/articles/components/HomeSectionWeiterlesen.svelte": 1, + "apps/mana/apps/web/src/lib/modules/articles/components/QuickAddInput.svelte": 1, + "apps/mana/apps/web/src/lib/modules/articles/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/articles/views/DetailView.svelte": 9, + "apps/mana/apps/web/src/lib/modules/articles/views/HighlightsView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/articles/widgets/ArticlesUnreadWidget.svelte": 1, + "apps/mana/apps/web/src/lib/modules/automations/ListView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/body/components/CalorieWeightChart.svelte": 1, + "apps/mana/apps/web/src/lib/modules/body/components/ExercisePicker.svelte": 6, + "apps/mana/apps/web/src/lib/modules/body/components/ExerciseProgressionChart.svelte": 1, + "apps/mana/apps/web/src/lib/modules/body/components/PhaseManager.svelte": 1, + "apps/mana/apps/web/src/lib/modules/body/components/RoutineManager.svelte": 3, + "apps/mana/apps/web/src/lib/modules/body/components/SetRow.svelte": 2, + "apps/mana/apps/web/src/lib/modules/body/components/WeightChart.svelte": 1, + "apps/mana/apps/web/src/lib/modules/body/widgets/BodyStatsWidget.svelte": 1, + "apps/mana/apps/web/src/lib/modules/broadcast/audience/AudienceBuilder.svelte": 2, + "apps/mana/apps/web/src/lib/modules/broadcast/components/SettingsForm.svelte": 3, + "apps/mana/apps/web/src/lib/modules/broadcast/editor/Editor.svelte": 3, + "apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/broadcast/views/ComposeView.svelte": 11, + "apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 9, + "apps/mana/apps/web/src/lib/modules/broadcast/widgets/BroadcastsWidget.svelte": 3, + "apps/mana/apps/web/src/lib/modules/calc/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/calendar/components/AgendaView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/calendar/components/CalendarHeader.svelte": 2, + "apps/mana/apps/web/src/lib/modules/calendar/components/CustomRecurrenceBuilder.svelte": 8, + "apps/mana/apps/web/src/lib/modules/calendar/components/DateStrip.svelte": 1, + "apps/mana/apps/web/src/lib/modules/calendar/components/EventCard.svelte": 5, + "apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte": 3, + "apps/mana/apps/web/src/lib/modules/calendar/components/EventForm.svelte": 10, + "apps/mana/apps/web/src/lib/modules/calendar/components/MiniCalendar.svelte": 2, + "apps/mana/apps/web/src/lib/modules/calendar/components/QuickEventPopover.svelte": 6, + "apps/mana/apps/web/src/lib/modules/calendar/components/SlotSuggestions.svelte": 3, + "apps/mana/apps/web/src/lib/modules/calendar/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/calendar/views/DetailView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte": 3, + "apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/chat/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/chat/views/DetailView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/citycorners/views/DetailView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/comic/components/BatchPanelEditor.svelte": 2, + "apps/mana/apps/web/src/lib/modules/comic/components/CharacterPicker.svelte": 6, + "apps/mana/apps/web/src/lib/modules/comic/components/PanelEditor.svelte": 3, + "apps/mana/apps/web/src/lib/modules/comic/components/PanelModelPicker.svelte": 1, + "apps/mana/apps/web/src/lib/modules/comic/components/PanelStrip.svelte": 1, + "apps/mana/apps/web/src/lib/modules/comic/components/ReferenceInputPicker.svelte": 1, + "apps/mana/apps/web/src/lib/modules/comic/components/StoryboardSuggester.svelte": 2, + "apps/mana/apps/web/src/lib/modules/comic/components/StoryCard.svelte": 1, + "apps/mana/apps/web/src/lib/modules/comic/components/StoryForm.svelte": 1, + "apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/comic/views/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/companion/components/CompanionChat.svelte": 1, + "apps/mana/apps/web/src/lib/modules/companion/components/RitualRunner.svelte": 5, + "apps/mana/apps/web/src/lib/modules/companion/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/complexity/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/contacts/components/pages/ContactPage.svelte": 2, + "apps/mana/apps/web/src/lib/modules/contacts/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/contacts/views/DetailView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/context/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/core/widgets/ActiveTimerWidget.svelte": 2, + "apps/mana/apps/web/src/lib/modules/core/widgets/NutritionProgressWidget.svelte": 5, + "apps/mana/apps/web/src/lib/modules/core/widgets/PlantWateringWidget.svelte": 3, + "apps/mana/apps/web/src/lib/modules/core/widgets/QuoteOfTheDayWidget.svelte": 2, + "apps/mana/apps/web/src/lib/modules/core/widgets/RecentChatsWidget.svelte": 2, + "apps/mana/apps/web/src/lib/modules/core/widgets/RecentContactsWidget.svelte": 2, + "apps/mana/apps/web/src/lib/modules/core/widgets/TasksTodayWidget.svelte": 1, + "apps/mana/apps/web/src/lib/modules/core/widgets/UpcomingEventsWidget.svelte": 1, + "apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 25, + "apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/drink/ListView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/events/components/BringListEditor.svelte": 2, + "apps/mana/apps/web/src/lib/modules/events/components/DiscoveredEventCard.svelte": 3, + "apps/mana/apps/web/src/lib/modules/events/components/DiscoverySetup.svelte": 2, + "apps/mana/apps/web/src/lib/modules/events/components/DiscoveryTab.svelte": 3, + "apps/mana/apps/web/src/lib/modules/events/components/EventCard.svelte": 3, + "apps/mana/apps/web/src/lib/modules/events/components/GuestListEditor.svelte": 2, + "apps/mana/apps/web/src/lib/modules/events/components/PublicRsvpList.svelte": 2, + "apps/mana/apps/web/src/lib/modules/events/components/RegionPicker.svelte": 1, + "apps/mana/apps/web/src/lib/modules/events/components/RsvpSummary.svelte": 3, + "apps/mana/apps/web/src/lib/modules/events/components/SourceManager.svelte": 5, + "apps/mana/apps/web/src/lib/modules/events/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/events/views/DetailView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/finance/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/firsts/ListView.svelte": 15, + "apps/mana/apps/web/src/lib/modules/goals/GoalEditor.svelte": 15, + "apps/mana/apps/web/src/lib/modules/goals/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/guides/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/guides/views/DetailView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/habits/components/HabitDetail.svelte": 4, + "apps/mana/apps/web/src/lib/modules/habits/components/HabitForm.svelte": 3, + "apps/mana/apps/web/src/lib/modules/habits/ListView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/inventory/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/inventory/views/DetailView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/invoices/components/ClientPicker.svelte": 3, + "apps/mana/apps/web/src/lib/modules/invoices/components/InvoiceForm.svelte": 10, + "apps/mana/apps/web/src/lib/modules/invoices/components/LinesEditor.svelte": 6, + "apps/mana/apps/web/src/lib/modules/invoices/components/SenderProfileForm.svelte": 14, + "apps/mana/apps/web/src/lib/modules/invoices/components/SendModal.svelte": 7, + "apps/mana/apps/web/src/lib/modules/invoices/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/invoices/views/DetailView.svelte": 18, + "apps/mana/apps/web/src/lib/modules/invoices/widgets/InvoicesOpenWidget.svelte": 3, + "apps/mana/apps/web/src/lib/modules/journal/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/kontext/KontextView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/library/components/EntryForm.svelte": 5, + "apps/mana/apps/web/src/lib/modules/library/components/ProgressControls.svelte": 3, + "apps/mana/apps/web/src/lib/modules/library/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/library/views/DetailView.svelte": 15, + "apps/mana/apps/web/src/lib/modules/library/views/GridView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/mail/ListView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/meditate/components/SessionPlayer.svelte": 3, + "apps/mana/apps/web/src/lib/modules/meditate/components/StatsOverview.svelte": 3, + "apps/mana/apps/web/src/lib/modules/meditate/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/memoro/views/DetailView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/mood/components/QuickLog.svelte": 6, + "apps/mana/apps/web/src/lib/modules/mood/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/moodlit/components/mood/CreateMoodDialog.svelte": 3, + "apps/mana/apps/web/src/lib/modules/music/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/music/views/DetailView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/myday/ListView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/news-research/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/news/ListView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/news/widgets/NewsUnreadWidget.svelte": 2, + "apps/mana/apps/web/src/lib/modules/notes/ListView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/photos/components/albums/CreateAlbumModal.svelte": 3, + "apps/mana/apps/web/src/lib/modules/photos/components/filters/FilterBar.svelte": 8, + "apps/mana/apps/web/src/lib/modules/photos/components/gallery/PhotoDetailModal.svelte": 9, + "apps/mana/apps/web/src/lib/modules/photos/ListView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/picture/components/ReferenceImagePicker.svelte": 1, + "apps/mana/apps/web/src/lib/modules/picture/ListView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/places/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/places/views/DetailView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/plants/views/DetailView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/playground/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/presi/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/presi/views/DetailView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/profile/components/MeImageSlotCard.svelte": 3, + "apps/mana/apps/web/src/lib/modules/profile/components/MeImageTile.svelte": 3, + "apps/mana/apps/web/src/lib/modules/profile/ContextFreeform.svelte": 7, + "apps/mana/apps/web/src/lib/modules/profile/ContextInterview.svelte": 6, + "apps/mana/apps/web/src/lib/modules/profile/ContextOverview.svelte": 35, + "apps/mana/apps/web/src/lib/modules/profile/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/profile/MeImagesView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/quiz/EditView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/quiz/PlayView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/recipes/ListView.svelte": 14, + "apps/mana/apps/web/src/lib/modules/research-lab/components/CompareColumn.svelte": 2, + "apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/rituals/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/skilltree/components/AchievementCelebration.svelte": 1, + "apps/mana/apps/web/src/lib/modules/skilltree/components/AddSkillModal.svelte": 1, + "apps/mana/apps/web/src/lib/modules/skilltree/components/AddXpModal.svelte": 1, + "apps/mana/apps/web/src/lib/modules/skilltree/components/EditSkillModal.svelte": 5, + "apps/mana/apps/web/src/lib/modules/skilltree/components/LevelUpCelebration.svelte": 1, + "apps/mana/apps/web/src/lib/modules/skilltree/components/StatsOverview.svelte": 4, + "apps/mana/apps/web/src/lib/modules/skilltree/views/DetailView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/sleep/components/MorningLog.svelte": 5, + "apps/mana/apps/web/src/lib/modules/sleep/ListView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/spaces/ListView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/spiral/ListView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/storage/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/storage/views/DetailView.svelte": 4, + "apps/mana/apps/web/src/lib/modules/stretch/components/AssessmentWizard.svelte": 2, + "apps/mana/apps/web/src/lib/modules/stretch/components/ReminderManager.svelte": 5, + "apps/mana/apps/web/src/lib/modules/stretch/components/RoutineCreator.svelte": 3, + "apps/mana/apps/web/src/lib/modules/stretch/components/SessionHistory.svelte": 5, + "apps/mana/apps/web/src/lib/modules/stretch/components/SessionPlayer.svelte": 6, + "apps/mana/apps/web/src/lib/modules/stretch/ListView.svelte": 13, + "apps/mana/apps/web/src/lib/modules/themes/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/times/components/clock/CircularProgress.svelte": 2, + "apps/mana/apps/web/src/lib/modules/times/components/clock/WorldMap.svelte": 1, + "apps/mana/apps/web/src/lib/modules/times/components/EntryForm.svelte": 1, + "apps/mana/apps/web/src/lib/modules/times/components/QuickStart.svelte": 1, + "apps/mana/apps/web/src/lib/modules/times/views/DetailView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/todo/components/board-views/ViewEditorModal.svelte": 1, + "apps/mana/apps/web/src/lib/modules/todo/components/form/TagSelector.svelte": 1, + "apps/mana/apps/web/src/lib/modules/todo/components/pages/PageEditBar.svelte": 5, + "apps/mana/apps/web/src/lib/modules/todo/components/pages/PagePicker.svelte": 2, + "apps/mana/apps/web/src/lib/modules/todo/components/pages/TodoPage.svelte": 1, + "apps/mana/apps/web/src/lib/modules/todo/components/SyncIndicator.svelte": 4, + "apps/mana/apps/web/src/lib/modules/todo/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/todo/views/DetailView.svelte": 8, + "apps/mana/apps/web/src/lib/modules/uload/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/uload/views/DetailView.svelte": 7, + "apps/mana/apps/web/src/lib/modules/wardrobe/components/GarmentForm.svelte": 4, + "apps/mana/apps/web/src/lib/modules/wardrobe/components/GarmentTryOnButton.svelte": 2, + "apps/mana/apps/web/src/lib/modules/wardrobe/components/OutfitComposer.svelte": 5, + "apps/mana/apps/web/src/lib/modules/wardrobe/components/TryOnButton.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wardrobe/components/TryOnModelPicker.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wardrobe/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailGarmentView.svelte": 12, + "apps/mana/apps/web/src/lib/modules/wardrobe/views/DetailOutfitView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/wardrobe/views/GridView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wardrobe/views/OutfitsView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/website/components/BlockInspector.svelte": 3, + "apps/mana/apps/web/src/lib/modules/website/components/DomainsSection.svelte": 4, + "apps/mana/apps/web/src/lib/modules/website/components/GalleryInspector.svelte": 9, + "apps/mana/apps/web/src/lib/modules/website/components/ImageInspector.svelte": 13, + "apps/mana/apps/web/src/lib/modules/website/components/InsertPalette.svelte": 1, + "apps/mana/apps/web/src/lib/modules/website/components/PageList.svelte": 7, + "apps/mana/apps/web/src/lib/modules/website/components/PublishBar.svelte": 4, + "apps/mana/apps/web/src/lib/modules/website/components/RollbackDialog.svelte": 6, + "apps/mana/apps/web/src/lib/modules/website/components/SiteSettingsDialog.svelte": 11, + "apps/mana/apps/web/src/lib/modules/website/components/TemplatePicker.svelte": 2, + "apps/mana/apps/web/src/lib/modules/website/ListView.svelte": 1, + "apps/mana/apps/web/src/lib/modules/website/views/EditorView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/website/views/SubmissionsView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/wetter/components/CurrentConditions.svelte": 2, + "apps/mana/apps/web/src/lib/modules/wetter/components/HourlyForecast.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wetter/components/LocationPicker.svelte": 5, + "apps/mana/apps/web/src/lib/modules/wetter/components/NowcastBar.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wetter/components/SourceComparison.svelte": 7, + "apps/mana/apps/web/src/lib/modules/wetter/components/WeatherAlerts.svelte": 1, + "apps/mana/apps/web/src/lib/modules/wetter/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/who/ListView.svelte": 5, + "apps/mana/apps/web/src/lib/modules/who/views/PlayView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte": 2, + "apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/writing/components/BriefingForm.svelte": 11, + "apps/mana/apps/web/src/lib/modules/writing/components/ExportMenu.svelte": 1, + "apps/mana/apps/web/src/lib/modules/writing/components/ReferencePicker.svelte": 3, + "apps/mana/apps/web/src/lib/modules/writing/components/RefinementPanel.svelte": 12, + "apps/mana/apps/web/src/lib/modules/writing/components/StyleForm.svelte": 2, + "apps/mana/apps/web/src/lib/modules/writing/components/VersionEditor.svelte": 1, + "apps/mana/apps/web/src/lib/modules/writing/components/VersionHistory.svelte": 1, + "apps/mana/apps/web/src/lib/modules/writing/views/DetailView.svelte": 6, + "apps/mana/apps/web/src/lib/modules/writing/views/ListView.svelte": 3, + "apps/mana/apps/web/src/lib/modules/writing/views/StylesView.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/admin/+layout.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/admin/user-data/[userId]/+page.svelte": 19, + "apps/mana/apps/web/src/routes/(app)/agents/templates/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/api-keys/+page.svelte": 14, + "apps/mana/apps/web/src/routes/(app)/articles/settings/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/broadcasts/[id]/edit/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/broadcasts/new/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/calc/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/calc/standard/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/calendar/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/calendar/calendars/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/calendar/event/[id]/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/cards/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/cards/decks/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/cards/explore/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/cards/progress/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/chat/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/chat/archive/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/chat/templates/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/citycorners/cities/[slug]/+layout.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/comic/new/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/companion/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/contacts/[id]/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/contacts/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/context/+page.svelte": 10, + "apps/mana/apps/web/src/routes/(app)/context/documents/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/context/documents/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/context/spaces/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/context/spaces/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/finance/+page.svelte": 10, + "apps/mana/apps/web/src/routes/(app)/food/[id]/+page.svelte": 10, + "apps/mana/apps/web/src/routes/(app)/food/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/food/add/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/food/goals/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/gifts/+page.svelte": 15, + "apps/mana/apps/web/src/routes/(app)/gifts/redeem/[code]/+page.svelte": 8, + "apps/mana/apps/web/src/routes/(app)/gifts/redeem/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/guides/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/habits/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/habits/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/inventory/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/inventory/categories/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/inventory/collections/[id]/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/inventory/collections/[id]/edit/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/inventory/collections/new/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/inventory/items/[id]/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/inventory/locations/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/inventory/search/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/invoices/[id]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/invoices/[id]/edit/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/invoices/new/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/invoices/settings/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/library/entry/[id]/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/llm-test/+page.svelte": 29, + "apps/mana/apps/web/src/routes/(app)/meditate/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/meditate/history/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/memoro/[id]/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/memoro/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/memoro/archive/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/memoro/tags/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/moodlit/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/moodlit/moods/+page.svelte": 9, + "apps/mana/apps/web/src/routes/(app)/moodlit/sequences/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/music/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/music/library/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/music/playlists/[id]/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/music/playlists/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/music/projects/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/news-research/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/news/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/news/+page.svelte": 10, + "apps/mana/apps/web/src/routes/(app)/news/add/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/news/preferences/+page.svelte": 8, + "apps/mana/apps/web/src/routes/(app)/news/saved/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/news/sources/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/notes/[id]/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/notes/+page.svelte": 8, + "apps/mana/apps/web/src/routes/(app)/observatory/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/onboarding/+layout.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/onboarding/look/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/onboarding/name/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/onboarding/templates/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/organizations/[id]/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/organizations/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/photos/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/photos/albums/[id]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/photos/albums/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/photos/favorites/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/photos/upload/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/picture/archive/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/picture/board/[id]/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/picture/board/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/picture/generate/+page.svelte": 5, + "apps/mana/apps/web/src/routes/(app)/plants/tags/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/playground/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/presi/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/presi/deck/[id]/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/presi/present/[id]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/questions/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/questions/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/questions/collections/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/questions/new/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/skilltree/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/skilltree/achievements/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/skilltree/tree/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/storage/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/storage/favorites/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/storage/files/[folderId]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/storage/files/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/storage/search/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/storage/trash/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/tags/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/teams/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/timeline/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/timeline/analytics/+page.svelte": 11, + "apps/mana/apps/web/src/routes/(app)/times/clients/[id]/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(app)/times/clock/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/times/projects/[id]/+page.svelte": 3, + "apps/mana/apps/web/src/routes/(app)/todo/+page.svelte": 1, + "apps/mana/apps/web/src/routes/(app)/todo/settings/+page.svelte": 13, + "apps/mana/apps/web/src/routes/(app)/uload/+page.svelte": 21, + "apps/mana/apps/web/src/routes/(app)/uload/analytics/[id]/+page.svelte": 20, + "apps/mana/apps/web/src/routes/(app)/uload/links/+page.svelte": 7, + "apps/mana/apps/web/src/routes/(app)/uload/settings/+page.svelte": 6, + "apps/mana/apps/web/src/routes/(app)/uload/tags/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/wardrobe/compose/[[outfitId]]/+page.svelte": 4, + "apps/mana/apps/web/src/routes/(app)/wetter/+page.svelte": 2, + "apps/mana/apps/web/src/routes/(auth)/reset-password/+page.svelte": 1, + "apps/mana/apps/web/src/routes/+error.svelte": 1, + "apps/mana/apps/web/src/routes/accept-invitation/+page.svelte": 7, + "apps/mana/apps/web/src/routes/auth/callback/+page.svelte": 3, + "apps/mana/apps/web/src/routes/auth/reset-password/+page.svelte": 1, + "apps/mana/apps/web/src/routes/g/[code]/+page.svelte": 6, + "apps/mana/apps/web/src/routes/rsvp/[token]/+page.svelte": 1, + "apps/mana/apps/web/src/routes/status/+page.svelte": 1, + "apps/mana/apps/web/src/routes/welcome/+page.svelte": 6 +} diff --git a/scripts/validate-no-hardcoded-strings.mjs b/scripts/validate-no-hardcoded-strings.mjs new file mode 100644 index 000000000..f646a541d --- /dev/null +++ b/scripts/validate-no-hardcoded-strings.mjs @@ -0,0 +1,125 @@ +#!/usr/bin/env node +/** + * Ratcheting validator for hardcoded German user-facing strings in + * apps/mana/apps/web Svelte components. Looks for German-looking text + * in attribute values (placeholder, title, aria-label, label, alt) and + * in text content, and compares per-file counts against a committed + * baseline. + * + * Every file's current count must be ≤ its baseline count. New files + * (not in baseline) must have 0 violations. The baseline can only + * shrink — fixing strings is rewarded, adding new ones fails CI. + * + * The validator is intentionally coarse: the goal is to stop the 1877- + * string backlog from growing while it's being whittled down, not to + * catch every translation miss perfectly. + * + * Usage: + * node scripts/validate-no-hardcoded-strings.mjs # check + * node scripts/validate-no-hardcoded-strings.mjs --update # rewrite baseline + */ + +import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, relative } from 'node:path'; +import { execSync } from 'node:child_process'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = join(__dirname, '..'); +const BASELINE_PATH = join(__dirname, 'i18n-hardcoded-baseline.json'); +const SCAN_GLOB = 'apps/mana/apps/web/src/**/*.svelte'; + +const PATTERNS = [ + /placeholder="([^"{}]*[äöüÄÖÜß][^"{}]*)"/g, + /title="([^"{}]*[äöüÄÖÜß][^"{}]*)"/g, + /aria-label="([^"{}]*[äöüÄÖÜß][^"{}]*)"/g, + /label="([^"{}]*[äöüÄÖÜß][^"{}]*)"/g, + /alt="([^"{}]*[äöüÄÖÜß][^"{}]*)"/g, + />([A-ZÄÖÜ][a-zäöüß][a-zäöüßÄÖÜA-Z ,.!?]{2,40}) 0) counts[f] = n; + } + return counts; +} + +function loadBaseline() { + if (!existsSync(BASELINE_PATH)) return {}; + return JSON.parse(readFileSync(BASELINE_PATH, 'utf8')); +} + +function main() { + const update = process.argv.includes('--update'); + const current = scan(); + const currentTotal = Object.values(current).reduce((a, b) => a + b, 0); + + if (update) { + const sorted = Object.fromEntries( + Object.entries(current).sort(([a], [b]) => a.localeCompare(b)) + ); + writeFileSync(BASELINE_PATH, JSON.stringify(sorted, null, 2) + '\n'); + console.log( + `✓ Baseline updated: ${currentTotal} violations across ${Object.keys(current).length} files.` + ); + return; + } + + const baseline = loadBaseline(); + const baselineTotal = Object.values(baseline).reduce((a, b) => a + b, 0); + const violations = []; + + for (const [file, n] of Object.entries(current)) { + const b = baseline[file] ?? 0; + if (n > b) violations.push({ file, current: n, baseline: b, delta: n - b }); + } + + if (violations.length > 0) { + console.error( + `\n✗ Hardcoded-string check FAILED — ${violations.length} file(s) exceed baseline:\n` + ); + for (const v of violations.slice(0, 20)) { + console.error(` ${v.file}: ${v.current} (was ${v.baseline}, +${v.delta})`); + } + if (violations.length > 20) console.error(` … +${violations.length - 20} more`); + console.error( + `\nYou added user-facing German strings to .svelte files without\n` + + `going through \$_('namespace.key'). Move them into locales/ or\n` + + `translate them inline, then re-run validate:i18n-hardcoded.\n` + + `If the additions are intentional (e.g. an untranslated dev-only\n` + + `page), run: pnpm run validate:i18n-hardcoded -- --update\n` + ); + process.exit(1); + } + + // Dropped below baseline? Tell the user so they can ratchet. + const shrunk = Object.keys(baseline).filter((f) => (current[f] ?? 0) < baseline[f]).length; + const cleaned = Object.keys(baseline).filter((f) => !(f in current)).length; + + console.log( + `✓ Hardcoded strings: ${currentTotal} violations across ${Object.keys(current).length} files ` + + `(baseline ${baselineTotal}).` + + (shrunk || cleaned + ? `\n ${shrunk} file(s) shrunk, ${cleaned} file(s) fully cleaned — ` + + `run 'pnpm run validate:i18n-hardcoded -- --update' to ratchet.` + : '') + ); +} + +main();