Skip to content

Views & Translations

Minimum Version Requirement

This feature requires WhatsMarkSaaS v2.0.0 or higher. Ensure your installation meets this requirement before proceeding.

Overview

This guide covers Blade template implementation and the multi-tenant translation system for custom modules. You will learn to create views, register view namespaces, implement translations, and support multi-language functionality.


View Registration

Service Provider Configuration

Views must be registered in the module's service provider:

php
<?php

namespace Modules\OrderTracker\Providers;

use Illuminate\Support\ServiceProvider;

class OrderTrackerServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Register views with namespace
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'ordertracker');
    }
}

View Namespace Convention

The view namespace must be lowercase module name without hyphens:

Module NameView Namespace
OrderTrackerordertracker
ProductCatalogproductcatalog
CustomerSupportcustomersupport

View Loading Order

Views are loaded using a priority system. The first matching view is used:

  1. Override location - resources/views/modules/ordertracker/
  2. Module location - Modules/OrderTracker/resources/views/

This allows theme customization without modifying module files.

Example:

php
// Laravel searches in this order:
// 1. resources/views/modules/ordertracker/index.blade.php
// 2. Modules/OrderTracker/resources/views/index.blade.php
return view('ordertracker::index');

Creating Views

Directory Structure

resources/views/
 index.blade.php          # Main index view
 create.blade.php         # Create form
 edit.blade.php           # Edit form
 show.blade.php           # Detail view
 livewire/
     order-list.blade.php

Basic View Template

blade
{{-- resources/views/index.blade.php --}}
<x-app-layout>
    <x-slot name="title">OrderTracker</x-slot>

    <div class="py-6">
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
            <!-- Module Header -->
            <div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg overflow-hidden mb-6">
                <div class="px-4 py-5 sm:px-6 bg-gradient-to-r from-indigo-500 to-purple-600">
                    <h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-slate-300  border-slate-200 dark:border-slate-700">OrderTracker Module</h3>
                    <p class="mt-1 max-w-2xl text-sm text-indigo-100">Module successfully installed and activated</p>
                </div>
                <div class="border-t border-gray-200 dark:border-gray-700 px-4 py-5 sm:p-6">
                    <div class="flex items-center text-sm text-gray-500 dark:text-gray-300 mb-4">
                        <svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                            <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                        </svg>
                        <span>Routes registered successfully</span>
                    </div>

                    <h2 class="text-lg font-medium text-gray-900 dark:text-white mb-3">Available Routes:</h2>

                    <!-- Routes Table -->
                    <div class="overflow-x-auto">
                        <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                            <thead class="bg-gray-50 dark:bg-gray-700">
                                <tr>
                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Method</th>
                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">URI</th>
                                    <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">Action</th>
                                </tr>
                            </thead>
                            <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
                                <tr>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">GET</span>
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">admin/ordertracker</td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">OrderTracker\Http\Controllers\OrderTrackerController@index</td>
                                </tr>
                                <tr>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">GET</span>
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">admin/ordertracker/create</td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">OrderTracker\Http\Controllers\OrderTrackerController@create</td>
                                </tr>
                                <tr>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">POST</span>
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">admin/ordertracker</td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">OrderTracker\Http\Controllers\OrderTrackerController@store</td>
                                </tr>
                                <tr>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
                                        <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">GET</span>
                                    </td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">admin/ordertracker/{id}</td>
                                    <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300">OrderTracker\Http\Controllers\OrderTrackerController@show</td>
                                </tr>
                            </tbody>
                        </table>
                    </div>

                    <!-- Documentation -->
                    <div class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-4">
                        <h3 class="text-md font-medium text-gray-900 dark:text-white mb-2">Module Information</h3>
                        <dl class="grid grid-cols-1 gap-x-4 gap-y-2 sm:grid-cols-2">
                            <div>
                                <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Module Name</dt>
                                <dd class="mt-1 text-sm text-gray-900 dark:text-white">OrderTracker</dd>
                            </div>
                            <div>
                                <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Module Type</dt>
                                <dd class="mt-1 text-sm text-gray-900 dark:text-white">Core</dd>
                            </div>
                            <div>
                                <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Status</dt>
                                <dd class="mt-1 text-sm text-gray-900 dark:text-white flex items-center">
                                    <svg class="w-4 h-4 mr-1 text-green-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
                                        <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                                    </svg>
                                    Active
                                </dd>
                            </div>
                            <div>
                                <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">Namespace</dt>
                                <dd class="mt-1 text-sm text-gray-900 dark:text-white">Modules\OrderTracker</dd>
                            </div>
                        </dl>
                    </div>
                </div>
                <div class="bg-gray-50 dark:bg-gray-700 px-4 py-4 sm:px-6 flex justify-between items-center">
                    <div class="text-sm text-gray-500 dark:text-gray-300">Use the navigation menu to access module features</div>
                    <a href="{{ url('admin/modules') }}" class="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                        All Modules
                    </a>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

Form View Template

blade
{{-- resources/views/create.blade.php --}}
<x-app-layout>
    <x-slot name="title">{{ t('Create Order') }}</x-slot>

    <div class="py-6">
        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
            <x-card>
                <x-slot:header>
                    <h3 class="text-base sm:text-lg font-semibold text-gray-900 dark:text-slate-300  border-slate-200 dark:border-slate-700">{{ t('Create Order') }}</h3>
                </x-slot:header>

                <x-slot:content>
                    @if($errors->any())
                        <div class="mb-4 p-4 bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 text-red-700 dark:text-red-400 rounded-md">
                            <ul class="list-disc list-inside space-y-1">
                                @foreach($errors->all() as $error)
                                    <li>{{ $error }}</li>
                                @endforeach
                            </ul>
                        </div>
                    @endif

                    <form action="{{ route('order-tracker.store') }}" method="POST" class="space-y-6">
                        @csrf

                        <div>
                            <x-label for="order_number" value="{{ t('Order Number') }}" />
                            <x-input 
                                type="text"
                                id="order_number"
                                name="order_number"
                                value="{{ old('order_number') }}"
                                required
                                class="w-full"
                            />
                            <x-input-error for="order_number" class="mt-2" />
                        </div>

                        <div>
                            <x-label for="total" value="{{ t('Total Amount') }}" />
                            <x-input 
                                type="number"
                                id="total"
                                name="total"
                                value="{{ old('total') }}"
                                step="0.01"
                                required
                                class="w-full"
                            />
                            <x-input-error for="total" class="mt-2" />
                        </div>

                        <div>
                            <x-label for="status" value="{{ t('Status') }}" />
                            <select 
                                id="status"
                                name="status"
                                required
                                class="block mt-1 w-full border-slate-300 rounded-md shadow-sm text-slate-900 sm:text-sm focus:ring-info-500 focus:border-info-500 dark:border-slate-500 dark:bg-slate-800 dark:text-slate-200 dark:focus:ring-info-500 dark:focus:border-info-500"
                            >
                                <option value="">{{ t('Select Status') }}</option>
                                <option value="pending" {{ old('status') == 'pending' ? 'selected' : '' }}>
                                    {{ t('Pending') }}
                                </option>
                                <option value="processing" {{ old('status') == 'processing' ? 'selected' : '' }}>
                                    {{ t('Processing') }}
                                </option>
                                <option value="completed" {{ old('status') == 'completed' ? 'selected' : '' }}>
                                    {{ t('Completed') }}
                                </option>
                            </select>
                            <x-input-error for="status" class="mt-2" />
                        </div>

                        <div class="flex items-center justify-end gap-3">
                            <x-button.secondary href="{{ route('order-tracker.index') }}">
                                {{ t('Cancel') }}
                            </x-button.secondary>
                            <x-button.loading-button type="submit" target="submit">
                                {{ t('Create Order') }}
                            </x-button.loading-button>
                        </div>
                    </form>
                </x-slot:content>
            </x-card>
        </div>
    </div>
</x-app-layout>

Rendering Views

From Controllers

php
public function index()
{
    $orders = Order::where('tenant_id', tenant_id())
        ->latest()
        ->paginate(20);

    return view('ordertracker::index', compact('orders'));
}

Passing Data

Single variable:

php
return view('ordertracker::show', ['order' => $order]);

Multiple variables:

php
return view('ordertracker::index', [
    'orders' => $orders,
    'statistics' => $statistics,
    'filters' => $filters,
]);

Using compact:

php
$orders = Order::all();
$statistics = $this->getStatistics();

return view('ordertracker::index', compact('orders', 'statistics'));

Translation System

Translation File Structure

WhatsMarkSaaS supports two translation file types:

FilePurposeLocation
en.jsonStandard translationsresources/lang/en.json
tenant_en.jsonTenant-specific translationsresources/lang/tenant_en.json

Standard Translations

File: resources/lang/en.json

json
{
    "Order Tracker": "Order Tracker",
    "View Orders": "View Orders",
    "Create Order": "Create Order",
    "Edit Order": "Edit Order",
    "Delete Order": "Delete Order",
    "Order Number": "Order Number",
    "Status": "Status",
    "Total": "Total",
    "Actions": "Actions",
    "Pending": "Pending",
    "Processing": "Processing",
    "Completed": "Completed",
    "No orders found": "No orders found",
    "Order created successfully": "Order created successfully",
    "Order updated successfully": "Order updated successfully",
    "Order deleted successfully": "Order deleted successfully"
}

Tenant-Specific Translations

File: resources/lang/tenant_en.json

json
{
    "welcome_message": "Welcome to Order Tracker",
    "dashboard_title": "Order Dashboard",
    "recent_orders": "Recent Orders"
}

Key differences:

  • Standard translations: Global strings used across all tenants
  • Tenant translations: Customizable per-tenant strings (stored in database)

Translation Registration

Service Provider Configuration

php
<?php

namespace Modules\OrderTracker\Providers;

use Illuminate\Support\ServiceProvider;

class OrderTrackerServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Register translation files
        $this->loadTranslationsFrom(
            module_path('OrderTracker', 'resources/lang'),
            'ordertracker'
        );
    }
}

Translation Loading Order

Translations are loaded with priority:

  1. Override location - resources/lang/modules/ordertracker/
  2. Module location - Modules/OrderTracker/resources/lang/

Using Translations

In Blade Templates

Standard translation helper:

blade
{{ t('Order Tracker') }}
{{ t('Create Order') }}
{{ t('No orders found') }}

With variables:

blade
{{ t('Order :number created', ['number' => $order->order_number]) }}

In Controllers

php
public function store(Request $request)
{
    // ... validation and storage logic ...

    return redirect()
        ->route('order-tracker.index')
        ->with('success', t('Order created successfully'));
}

In Livewire Components

php
<?php

namespace Modules\OrderTracker\Livewire;

use Livewire\Component;

class OrderList extends Component
{
    public function deleteOrder($orderId)
    {
        // ... deletion logic ...

        session()->flash('success', t('Order deleted successfully'));
    }

    public function render()
    {
        return view('ordertracker::livewire.order-list', [
            'title' => t('Order Tracker'),
        ]);
    }
}

Multi-Language Support

Language Detection

WhatsMarkSaaS automatically detects the current language based on:

  1. User language preference (database setting)
  2. Tenant default language
  3. Application default language (config/app.php)

View Components

Creating Blade Components

Component class:

php
<?php

namespace Modules\OrderTracker\View\Components;

use Illuminate\View\Component;

class OrderCard extends Component
{
    public $order;

    public function __construct($order)
    {
        $this->order = $order;
    }

    public function render()
    {
        return view('ordertracker::components.order-card');
    }
}

Component view:

blade
{{-- resources/views/components/order-card.blade.php --}}
<div class="card">
    <div class="card-header">
        <h5>{{ t('Order') }} #{{ $order->order_number }}</h5>
    </div>
    <div class="card-body">
        <p><strong>{{ t('Status') }}:</strong> {{ $order->status }}</p>
        <p><strong>{{ t('Total') }}:</strong> {{ $order->total }}</p>
    </div>
</div>

Register component in service provider:

php
use Illuminate\Support\Facades\Blade;

public function boot(): void
{
    Blade::component('order-tracker::order-card', OrderCard::class);
}

Usage in views:

blade
<x-order-tracker::order-card :order="$order" />

Livewire Component Views

Creating Livewire Component View

Component class:

php
<?php

namespace Modules\OrderTracker\Livewire;

use Livewire\Component;
use Livewire\WithPagination;
use Modules\OrderTracker\Models\Order;

class OrderList extends Component
{
    use WithPagination;

    public $search = '';

    public function render()
    {
        $orders = Order::where('tenant_id', tenant_id())
            ->when($this->search, function($query) {
                $query->where('order_number', 'like', '%'.$this->search.'%');
            })
            ->latest()
            ->paginate(10);

        return view('ordertracker::livewire.order-list', compact('orders'));
    }
}

Component view:

blade
{{-- resources/views/livewire/order-list.blade.php --}}
<div class="space-y-4">
    <x-card class="rounded-lg">
        <x-slot:header>
            <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
                <h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200">
                    {{ t('OrderTracker::messages.orders') }}
                </h3>
            </div>
        </x-slot:header>

        <x-slot:content>
            <div class="space-y-6">
                <div class="w-full max-w-md">
                    <x-input 
                        type="text"
                        wire:model.live="search"
                        placeholder="{{ t('OrderTracker::messages.search_by_order_number') }}"
                    />
                </div>

                <div class="rounded-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
                    <div class="overflow-x-auto">
                        <table class="w-full divide-y divide-slate-200 dark:divide-slate-700">
                            <thead class="bg-slate-50 dark:bg-slate-800">
                                <tr>
                                    <th class="py-3.5 pl-6 pr-3 text-left text-xs font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wide">
                                        {{ t('OrderTracker::messages.order_number') }}
                                    </th>
                                    <th class="px-3 py-3.5 text-left text-xs font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wide">
                                        {{ t('OrderTracker::messages.status') }}
                                    </th>
                                    <th class="px-3 py-3.5 text-left text-xs font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wide">
                                        {{ t('OrderTracker::messages.total') }}
                                    </th>
                                    <th class="py-3.5 pl-3 pr-6 text-left text-xs font-semibold text-slate-700 dark:text-slate-300 uppercase tracking-wide">
                                        {{ t('OrderTracker::messages.actions') }}
                                    </th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-200 dark:divide-slate-700 bg-white dark:bg-slate-900">
                                @forelse($orders as $order)
                                    <tr class="hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors duration-150">
                                        <td class="py-4 pl-6 pr-3 text-sm font-medium text-slate-900 dark:text-slate-100 whitespace-nowrap">
                                            {{ $order->order_number }}
                                        </td>
                                        <td class="px-3 py-4 whitespace-nowrap">
                                            @php
                                                $statusColors = [
                                                    'completed' => 'bg-success-100 text-success-800 dark:bg-success-900/30 dark:text-success-400',
                                                    'processing' => 'bg-info-100 text-info-800 dark:bg-info-900/30 dark:text-info-400',
                                                    'pending' => 'bg-warning-100 text-warning-800 dark:bg-warning-900/30 dark:text-warning-400',
                                                ];
                                                $colorClass = $statusColors[$order->status] ?? 'bg-slate-100 text-slate-800 dark:bg-slate-900/30 dark:text-slate-400';
                                            @endphp
                                            <span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium {{ $colorClass }}">
                                                {{ t('OrderTracker::messages.'.$order->status) }}
                                            </span>
                                        </td>
                                        <td class="px-3 py-4 text-sm text-slate-700 dark:text-slate-300 whitespace-nowrap">
                                            {{ $order->total }}
                                        </td>
                                        <td class="py-4 pl-3 pr-6 whitespace-nowrap">
                                            <x-button.secondary href="{{ route('order-tracker.show', $order->id) }}" class="text-sm">
                                                {{ t('OrderTracker::messages.view') }}
                                            </x-button.secondary>
                                        </td>
                                    </tr>
                                @empty
                                    <tr>
                                        <td colspan="4" class="py-12">
                                            <div class="flex flex-col items-center justify-center text-center">
                                                <div class="w-16 h-16 mb-4 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center">
                                                    <svg class="w-8 h-8 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                                                    </svg>
                                                </div>
                                                <p class="text-sm font-medium text-slate-600 dark:text-slate-400">
                                                    {{ t('OrderTracker::messages.no_orders_found') }}
                                                </p>
                                            </div>
                                        </td>
                                    </tr>
                                @endforelse
                            </tbody>
                        </table>
                    </div>
                </div>

                @if($orders->hasPages())
                    <div class="mt-6">
                        {{ $orders->links() }}
                    </div>
                @endif
            </div>
        </x-slot:content>
    </x-card>
</div>

View Publishing

Publishing Views for Customization

Allow users to customize module views:

Register publishable views in service provider:

php
public function boot(): void
{
    // Register views
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'ordertracker');

    // Publish views
    $this->publishes([
        module_path('OrderTracker', 'resources/views') => resource_path('views/modules/ordertracker'),
    ], 'ordertracker-views');
}

Publish views:

bash
php artisan vendor:publish --tag=ordertracker-views

Published views will be located at resources/views/modules/ordertracker/ and take precedence over module views.


Translation Helper Reference

HelperPurposeExample
t()Standard translationt('Order Tracker')
tenant_setting()Get tenant-specific settingtenant_setting('language')

Best Practices

View Organization

  1. Use layouts - Extend base layouts instead of duplicating HTML
  2. Component reusability - Create Blade components for repeated UI elements
  3. Partial views - Extract complex sections into partials
  4. Consistent naming - Use descriptive view filenames

Translation Management

  1. Complete translations - Translate all user-facing strings
  2. Consistent keys - Use descriptive translation keys
  3. Avoid hardcoded text - Always use t() helper for displayed text
  4. Test all languages - Verify translations render correctly

Performance

  1. View caching - Clear view cache after updates: php artisan view:clear
  2. Lazy loading - Load translations on-demand
  3. Minimize queries - Pass all required data from controller

Troubleshooting

Views not found

Error: View [ordertracker::index] not found

Solutions:

  1. Verify view file exists at Modules/OrderTracker/resources/views/index.blade.php
  2. Verify service provider registers views: $this->loadViewsFrom(...)
  3. Clear view cache: php artisan view:clear
  4. Check namespace is lowercase: ordertracker:: not OrderTracker::

Translations not loading

Error: Translation keys display instead of translated strings

Solutions:

  1. Verify translation files exist in resources/lang/
  2. Check JSON syntax (no trailing commas)
  3. Clear cache: php artisan cache:clear
  4. Verify service provider registers translations: $this->loadTranslationsFrom(...)

Next Steps


This guide is for WhatsMarkSaaS v2.0.0+ custom module development.

© 2024 - Corbital Technologies. All rights reserved.