1
0

feat: module-owned i18n with merge-at-boot translation loading

Each module can now ship its own i18n/ directory with translation files
(default_de.json, default_en.json). At boot time, ModuleI18nLoader
preloads core translations then merges module translations on top via
Reflection into I18n::$strings — the t() helper stays unchanged.

- Add i18nPath property to ModuleManifest
- Add ModuleI18nLoader with Reflection-based preload
- Extract 59 module-exclusive keys from core i18n into module files
  (notifications: 8, bookmarks: 41, addressbook: 10)
- Expand TranslationKeysTest to scan modules/ and validate against
  merged core + module translations
- Add ModuleI18nContractTest for per-module de/en parity and completeness

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:08:34 +01:00
parent 3147009498
commit 1f0b1caf54
16 changed files with 601 additions and 133 deletions

View File

@@ -83,6 +83,8 @@ final class ModuleManifest
public readonly ?string $migrationsPath;
public readonly ?string $i18nPath;
public readonly string $basePath;
/**
@@ -128,6 +130,11 @@ final class ModuleManifest
$this->migrationsPath = is_string($migrationsPath) && trim($migrationsPath) !== ''
? rtrim($basePath, '/') . '/' . ltrim(trim($migrationsPath), '/')
: null;
$i18nPath = $raw['i18n_path'] ?? null;
$this->i18nPath = is_string($i18nPath) && trim($i18nPath) !== ''
? rtrim($basePath, '/') . '/' . ltrim(trim($i18nPath), '/')
: null;
}
/**