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
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 Name | View Namespace |
|---|---|
| OrderTracker | ordertracker |
| ProductCatalog | productcatalog |
| CustomerSupport | customersupport |
View Loading Order
Views are loaded using a priority system. The first matching view is used:
- Override location -
resources/views/modules/ordertracker/ - Module location -
Modules/OrderTracker/resources/views/
This allows theme customization without modifying module files.
Example:
// 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.phpBasic View Template
{{-- 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
{{-- 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
public function index()
{
$orders = Order::where('tenant_id', tenant_id())
->latest()
->paginate(20);
return view('ordertracker::index', compact('orders'));
}Passing Data
Single variable:
return view('ordertracker::show', ['order' => $order]);Multiple variables:
return view('ordertracker::index', [
'orders' => $orders,
'statistics' => $statistics,
'filters' => $filters,
]);Using compact:
$orders = Order::all();
$statistics = $this->getStatistics();
return view('ordertracker::index', compact('orders', 'statistics'));Translation System
Translation File Structure
WhatsMarkSaaS supports two translation file types:
| File | Purpose | Location |
|---|---|---|
en.json | Standard translations | resources/lang/en.json |
tenant_en.json | Tenant-specific translations | resources/lang/tenant_en.json |
Standard Translations
File: resources/lang/en.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
{
"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
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:
- Override location -
resources/lang/modules/ordertracker/ - Module location -
Modules/OrderTracker/resources/lang/
Using Translations
In Blade Templates
Standard translation helper:
{{ t('Order Tracker') }}
{{ t('Create Order') }}
{{ t('No orders found') }}With variables:
{{ t('Order :number created', ['number' => $order->order_number]) }}In Controllers
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
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:
- User language preference (database setting)
- Tenant default language
- Application default language (config/app.php)
View Components
Creating Blade Components
Component class:
<?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:
{{-- 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:
use Illuminate\Support\Facades\Blade;
public function boot(): void
{
Blade::component('order-tracker::order-card', OrderCard::class);
}Usage in views:
<x-order-tracker::order-card :order="$order" />Livewire Component Views
Creating Livewire Component View
Component class:
<?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:
{{-- 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:
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:
php artisan vendor:publish --tag=ordertracker-viewsPublished views will be located at resources/views/modules/ordertracker/ and take precedence over module views.
Translation Helper Reference
| Helper | Purpose | Example |
|---|---|---|
t() | Standard translation | t('Order Tracker') |
tenant_setting() | Get tenant-specific setting | tenant_setting('language') |
Best Practices
View Organization
- Use layouts - Extend base layouts instead of duplicating HTML
- Component reusability - Create Blade components for repeated UI elements
- Partial views - Extract complex sections into partials
- Consistent naming - Use descriptive view filenames
Translation Management
- Complete translations - Translate all user-facing strings
- Consistent keys - Use descriptive translation keys
- Avoid hardcoded text - Always use
t()helper for displayed text - Test all languages - Verify translations render correctly
Performance
- View caching - Clear view cache after updates:
php artisan view:clear - Lazy loading - Load translations on-demand
- Minimize queries - Pass all required data from controller
Troubleshooting
Views not found
Error: View [ordertracker::index] not found
Solutions:
- Verify view file exists at
Modules/OrderTracker/resources/views/index.blade.php - Verify service provider registers views:
$this->loadViewsFrom(...) - Clear view cache:
php artisan view:clear - Check namespace is lowercase:
ordertracker::notOrderTracker::
Translations not loading
Error: Translation keys display instead of translated strings
Solutions:
- Verify translation files exist in
resources/lang/ - Check JSON syntax (no trailing commas)
- Clear cache:
php artisan cache:clear - Verify service provider registers translations:
$this->loadTranslationsFrom(...)
Next Steps
- Hooks & Events - Integrate with hook system
- Helper Functions - Use module utilities
This guide is for WhatsMarkSaaS v2.0.0+ custom module development.