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:
12
modules/addressbook/i18n/default_de.json
Normal file
12
modules/addressbook/i18n/default_de.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"About": "Über",
|
||||
"Address book": "Adressbuch",
|
||||
"Call mobile": "Mobil anrufen",
|
||||
"Call phone": "Telefon anrufen",
|
||||
"From": "Von",
|
||||
"No departments": "Keine Abteilungen",
|
||||
"Quick actions": "Schnellaktionen",
|
||||
"Send email": "E-Mail senden",
|
||||
"To": "Bis",
|
||||
"View in address book": "Im Adressbuch ansehen"
|
||||
}
|
||||
12
modules/addressbook/i18n/default_en.json
Normal file
12
modules/addressbook/i18n/default_en.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"About": "About",
|
||||
"Address book": "Address book",
|
||||
"Call mobile": "Call mobile",
|
||||
"Call phone": "Call phone",
|
||||
"From": "From",
|
||||
"No departments": "No departments",
|
||||
"Quick actions": "Quick actions",
|
||||
"Send email": "Send email",
|
||||
"To": "To",
|
||||
"View in address book": "View in address book"
|
||||
}
|
||||
@@ -94,4 +94,5 @@ return [
|
||||
],
|
||||
|
||||
'migrations_path' => 'db/updates',
|
||||
'i18n_path' => 'i18n',
|
||||
];
|
||||
|
||||
43
modules/bookmarks/i18n/default_de.json
Normal file
43
modules/bookmarks/i18n/default_de.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Bookmarks": "Lesezeichen",
|
||||
"Bookmark this page": "Seite als Lesezeichen speichern",
|
||||
"Save bookmark": "Lesezeichen speichern",
|
||||
"Bookmark name": "Lesezeichen-Name",
|
||||
"Delete bookmark": "Lesezeichen löschen",
|
||||
"Delete this bookmark?": "Dieses Lesezeichen wirklich löschen?",
|
||||
"No bookmarks yet": "Noch keine Lesezeichen",
|
||||
"New group": "Neue Gruppe",
|
||||
"No group": "Keine Gruppe",
|
||||
"Assign to group": "Gruppe zuweisen",
|
||||
"Group name": "Gruppenname",
|
||||
"Delete group": "Gruppe löschen",
|
||||
"Group": "Gruppe",
|
||||
"Icon": "Symbol",
|
||||
"Maximum bookmarks reached": "Maximale Anzahl an Lesezeichen erreicht",
|
||||
"Edit bookmark": "Lesezeichen bearbeiten",
|
||||
"Save changes": "Änderungen speichern",
|
||||
"Bookmark saved": "Lesezeichen gespeichert",
|
||||
"Bookmark updated": "Lesezeichen aktualisiert",
|
||||
"Bookmark deleted": "Lesezeichen gelöscht",
|
||||
"Group created": "Gruppe erstellt",
|
||||
"Group deleted": "Gruppe gelöscht",
|
||||
"Group delete failed": "Gruppe konnte nicht gelöscht werden",
|
||||
"Delete group and keep bookmarks?": "Gruppe löschen und enthaltene Lesezeichen behalten?",
|
||||
"Bookmark action failed": "Lesezeichen-Aktion fehlgeschlagen",
|
||||
"Group action failed": "Gruppen-Aktion fehlgeschlagen",
|
||||
"Reorder saved": "Sortierung gespeichert",
|
||||
"Reorder failed": "Sortierung fehlgeschlagen",
|
||||
"Move group up": "Gruppe nach oben",
|
||||
"Move group down": "Gruppe nach unten",
|
||||
"Move bookmark up": "Lesezeichen nach oben",
|
||||
"Move bookmark down": "Lesezeichen nach unten",
|
||||
"Group actions": "Gruppenaktionen",
|
||||
"Bookmark actions": "Lesezeichenaktionen",
|
||||
"Bookmark name is required": "Lesezeichen-Name ist erforderlich",
|
||||
"Bookmark URL is invalid": "Lesezeichen-URL ist ungültig",
|
||||
"Group not found": "Gruppe nicht gefunden",
|
||||
"Edit group": "Gruppe bearbeiten",
|
||||
"Group updated": "Gruppe aktualisiert",
|
||||
"Group name is required": "Gruppenname ist erforderlich",
|
||||
"Maximum groups reached": "Maximale Anzahl an Gruppen erreicht"
|
||||
}
|
||||
43
modules/bookmarks/i18n/default_en.json
Normal file
43
modules/bookmarks/i18n/default_en.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"Bookmarks": "Bookmarks",
|
||||
"Bookmark this page": "Bookmark this page",
|
||||
"Save bookmark": "Save bookmark",
|
||||
"Bookmark name": "Bookmark name",
|
||||
"Delete bookmark": "Delete bookmark",
|
||||
"Delete this bookmark?": "Delete this bookmark?",
|
||||
"No bookmarks yet": "No bookmarks yet",
|
||||
"New group": "New group",
|
||||
"No group": "No group",
|
||||
"Assign to group": "Assign to group",
|
||||
"Group name": "Group name",
|
||||
"Delete group": "Delete group",
|
||||
"Group": "Group",
|
||||
"Icon": "Icon",
|
||||
"Maximum bookmarks reached": "Maximum bookmarks reached",
|
||||
"Edit bookmark": "Edit bookmark",
|
||||
"Save changes": "Save changes",
|
||||
"Bookmark saved": "Bookmark saved",
|
||||
"Bookmark updated": "Bookmark updated",
|
||||
"Bookmark deleted": "Bookmark deleted",
|
||||
"Group created": "Group created",
|
||||
"Group deleted": "Group deleted",
|
||||
"Group delete failed": "Group delete failed",
|
||||
"Delete group and keep bookmarks?": "Delete group and keep bookmarks?",
|
||||
"Bookmark action failed": "Bookmark action failed",
|
||||
"Group action failed": "Group action failed",
|
||||
"Reorder saved": "Reorder saved",
|
||||
"Reorder failed": "Reorder failed",
|
||||
"Move group up": "Move group up",
|
||||
"Move group down": "Move group down",
|
||||
"Move bookmark up": "Move bookmark up",
|
||||
"Move bookmark down": "Move bookmark down",
|
||||
"Group actions": "Group actions",
|
||||
"Bookmark actions": "Bookmark actions",
|
||||
"Bookmark name is required": "Bookmark name is required",
|
||||
"Bookmark URL is invalid": "Bookmark URL is invalid",
|
||||
"Group not found": "Group not found",
|
||||
"Edit group": "Edit group",
|
||||
"Group updated": "Group updated",
|
||||
"Group name is required": "Group name is required",
|
||||
"Maximum groups reached": "Maximum groups reached"
|
||||
}
|
||||
@@ -130,4 +130,5 @@ return [
|
||||
|
||||
'permissions' => [],
|
||||
'migrations_path' => null,
|
||||
'i18n_path' => 'i18n',
|
||||
];
|
||||
|
||||
10
modules/notifications/i18n/default_de.json
Normal file
10
modules/notifications/i18n/default_de.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Notifications": "Benachrichtigungen",
|
||||
"No notifications": "Keine Benachrichtigungen",
|
||||
"Mark all as read": "Alle als gelesen markieren",
|
||||
"Mark as read": "Als gelesen markieren",
|
||||
"Dismiss": "Verwerfen",
|
||||
"Action failed": "Aktion fehlgeschlagen",
|
||||
"New user: %s": "Neuer Benutzer: %s",
|
||||
"User deleted: %s": "Benutzer gelöscht: %s"
|
||||
}
|
||||
10
modules/notifications/i18n/default_en.json
Normal file
10
modules/notifications/i18n/default_en.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Notifications": "Notifications",
|
||||
"No notifications": "No notifications",
|
||||
"Mark all as read": "Mark all as read",
|
||||
"Mark as read": "Mark as read",
|
||||
"Dismiss": "Dismiss",
|
||||
"Action failed": "Action failed",
|
||||
"New user: %s": "New user: %s",
|
||||
"User deleted: %s": "User deleted: %s"
|
||||
}
|
||||
115
modules/notifications/module.php
Normal file
115
modules/notifications/module.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Notifications module manifest.
|
||||
*
|
||||
* In-app notifications with bell icon, dropdown panel,
|
||||
* event-driven creation, and scheduled cleanup.
|
||||
*/
|
||||
return [
|
||||
'id' => 'notifications',
|
||||
'version' => '1.0.0',
|
||||
'enabled_by_default' => true,
|
||||
'load_order' => 15,
|
||||
'requires' => [],
|
||||
|
||||
'routes' => [
|
||||
['path' => 'notifications/list-data', 'target' => 'notifications/list-data'],
|
||||
['path' => 'notifications/mark-read-data', 'target' => 'notifications/mark-read-data'],
|
||||
['path' => 'notifications/delete-data', 'target' => 'notifications/delete-data'],
|
||||
['path' => 'notifications/unread-count-data', 'target' => 'notifications/unread-count-data'],
|
||||
],
|
||||
'public_paths' => [],
|
||||
|
||||
'container_registrars' => [
|
||||
\MintyPHP\Module\Notifications\NotificationsContainerRegistrar::class,
|
||||
],
|
||||
|
||||
'ui_slots' => [
|
||||
'topbar.right_item' => [
|
||||
[
|
||||
'key' => 'notifications-bell',
|
||||
'template' => 'templates/topbar-notification-bell.phtml',
|
||||
'permission' => 'notifications.view',
|
||||
'order' => 100,
|
||||
],
|
||||
],
|
||||
'layout.head_style' => [
|
||||
[
|
||||
'key' => 'notifications-topbar-style',
|
||||
'path' => 'modules/notifications/css/layout/app-topbar-notifications.css',
|
||||
'permission' => 'notifications.view',
|
||||
'order' => 100,
|
||||
],
|
||||
[
|
||||
'key' => 'notifications-dropdown-style',
|
||||
'path' => 'modules/notifications/css/components/app-notification-dropdown.css',
|
||||
'permission' => 'notifications.view',
|
||||
'order' => 101,
|
||||
],
|
||||
],
|
||||
'runtime.component' => [
|
||||
[
|
||||
'key' => 'notification-bell',
|
||||
'script' => 'modules/notifications/js/components/app-notification-bell.js',
|
||||
'export' => 'initNotificationBell',
|
||||
'scope' => 'global',
|
||||
'config_path' => 'components.notificationBell',
|
||||
'default_config' => [],
|
||||
'phase' => 'late',
|
||||
'permission' => 'notifications.view',
|
||||
'order' => 100,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'authorization_policies' => [
|
||||
\MintyPHP\Module\Notifications\NotificationsAuthorizationPolicy::class,
|
||||
],
|
||||
|
||||
'permissions' => [
|
||||
[
|
||||
'key' => 'notifications.view',
|
||||
'label' => 'Notifications: View',
|
||||
'description' => 'View and manage own notifications',
|
||||
],
|
||||
],
|
||||
|
||||
'event_listeners' => [
|
||||
'user.created' => [
|
||||
['class' => \MintyPHP\Module\Notifications\Listeners\UserCreatedNotificationListener::class, 'method' => 'handle'],
|
||||
],
|
||||
'user.deleted' => [
|
||||
['class' => \MintyPHP\Module\Notifications\Listeners\UserDeletedNotificationListener::class, 'method' => 'handle'],
|
||||
],
|
||||
],
|
||||
|
||||
'scheduler_jobs' => [
|
||||
[
|
||||
'job_key' => 'notifications_cleanup',
|
||||
'handler' => \MintyPHP\Module\Notifications\Handler\NotificationCleanupJobHandler::class,
|
||||
'label' => 'Notification cleanup',
|
||||
'description' => 'Purges read notifications older than 90 days',
|
||||
'default_enabled' => 1,
|
||||
'default_timezone' => 'UTC',
|
||||
'default_schedule_type' => 'daily',
|
||||
'default_schedule_interval' => 1,
|
||||
'default_schedule_time' => '04:00',
|
||||
'default_schedule_weekdays_csv' => null,
|
||||
'default_catchup_once' => 1,
|
||||
'allowed_schedule_types' => ['daily', 'weekly'],
|
||||
],
|
||||
],
|
||||
|
||||
'layout_context_providers' => [
|
||||
\MintyPHP\Module\Notifications\Providers\NotificationsLayoutProvider::class,
|
||||
],
|
||||
'session_providers' => [
|
||||
\MintyPHP\Module\Notifications\Providers\NotificationsSessionProvider::class,
|
||||
],
|
||||
|
||||
'search_resources' => [],
|
||||
'asset_groups' => [],
|
||||
'migrations_path' => 'db/updates',
|
||||
'i18n_path' => 'i18n',
|
||||
];
|
||||
Reference in New Issue
Block a user