From 345bfde3064b9d13c2ee7283c00ab54723d278e0 Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Fri, 10 Apr 2026 21:30:00 +0200 Subject: [PATCH] feat(modules): translated display names and inline settings modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CompanyModulesController attaches a translated display_name to each module before returning the list. ModuleSettingsController gains a translateSchema() helper that resolves section titles and field labels against the host app's i18n store before sending the schema to the frontend, so module authors can keep their 'my_module::settings.field' keys and users still see localized strings. Per-module settings now open in an inline ModuleSettingsModal rather than routing to a standalone page. The modal reuses BaseSchemaForm for rendering, so the whole interaction takes place in-context next to the module card the user clicked — no navigation, no loss of place. CompanyModuleCard displays the translated display_name instead of the raw slug and emits open-settings with the module payload; the parent view hands that to the modal store. --- .../Modules/CompanyModulesController.php | 26 +++- .../Modules/ModuleSettingsController.php | 27 +++- .../modules/components/CompanyModuleCard.vue | 13 +- .../components/ModuleSettingsModal.vue | 136 ++++++++++++++++++ .../scripts/features/company/modules/store.ts | 1 + 5 files changed, 193 insertions(+), 10 deletions(-) create mode 100644 resources/scripts/features/company/modules/components/ModuleSettingsModal.vue diff --git a/app/Http/Controllers/Company/Modules/CompanyModulesController.php b/app/Http/Controllers/Company/Modules/CompanyModulesController.php index 48dedae1..2154d4de 100644 --- a/app/Http/Controllers/Company/Modules/CompanyModulesController.php +++ b/app/Http/Controllers/Company/Modules/CompanyModulesController.php @@ -36,17 +36,41 @@ class CompanyModulesController extends Controller ->get() ->map(function (Module $module) { $slug = Str::kebab($module->name); + $menu = ModuleRegistry::menuFor($slug); + $translatedMenuTitle = $this->translateMenuTitle($menu['title'] ?? null); + $displayName = $translatedMenuTitle ?? Str::headline($module->name); return [ 'slug' => $slug, 'name' => $module->name, + 'display_name' => $displayName, 'version' => $module->version, 'has_settings' => ModuleRegistry::settingsFor($slug) !== null, - 'menu' => ModuleRegistry::menuFor($slug), + 'menu' => $menu === null + ? null + : [ + ...$menu, + 'title' => $translatedMenuTitle ?? $menu['title'], + ], ]; }) ->values(); return response()->json(['data' => $modules]); } + + private function translateMenuTitle(?string $title): ?string + { + if ($title === null) { + return null; + } + + $translatedTitle = __($title); + + if (! is_string($translatedTitle) || $translatedTitle === $title) { + return null; + } + + return $translatedTitle; + } } diff --git a/app/Http/Controllers/Company/Modules/ModuleSettingsController.php b/app/Http/Controllers/Company/Modules/ModuleSettingsController.php index d3a6612b..0b8723c8 100644 --- a/app/Http/Controllers/Company/Modules/ModuleSettingsController.php +++ b/app/Http/Controllers/Company/Modules/ModuleSettingsController.php @@ -43,7 +43,7 @@ class ModuleSettingsController extends Controller ->all(); return response()->json([ - 'schema' => $schema->toArray(), + 'schema' => $this->translateSchema($schema->toArray()), 'values' => $values, ]); } @@ -136,6 +136,31 @@ class ModuleSettingsController extends Controller * without losing information. Reads happen in show() above and naturally * return strings; the frontend handles re-coercion in BaseSchemaForm.vue. */ + /** + * Translate section titles and field labels in the schema so the + * frontend receives ready-to-display strings instead of Laravel + * translation keys it cannot resolve (e.g. `helloworld::settings.greeting`). + * + * @param array{sections: list>} $schema + * @return array{sections: list>} + */ + private function translateSchema(array $schema): array + { + foreach ($schema['sections'] as &$section) { + if (isset($section['title'])) { + $section['title'] = __($section['title']); + } + + foreach ($section['fields'] as &$field) { + if (isset($field['label'])) { + $field['label'] = __($field['label']); + } + } + } + + return $schema; + } + private function normalizeForStorage(mixed $value): string { if (is_bool($value)) { diff --git a/resources/scripts/features/company/modules/components/CompanyModuleCard.vue b/resources/scripts/features/company/modules/components/CompanyModuleCard.vue index 9364d6ad..ba92d69a 100644 --- a/resources/scripts/features/company/modules/components/CompanyModuleCard.vue +++ b/resources/scripts/features/company/modules/components/CompanyModuleCard.vue @@ -14,7 +14,7 @@
-

{{ data.name }}

+

{{ data.display_name }}

{{ $t('modules.version') }} {{ data.version }}

@@ -25,7 +25,7 @@ v-if="data.has_settings" size="sm" variant="primary-outline" - @click="goToSettings" + @click="emit('open-settings', data)" >