shammaa/laravel-slug

Professional Arabic-friendly slug generator for Laravel with automatic model integration

Installs: 14

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/shammaa/laravel-slug

1.4.1 2025-12-17 09:41 UTC

This package is auto-updated.

Last update: 2025-12-17 09:41:34 UTC


README

A simple, professional slug generator for Laravel. Takes text → generates slug. That's it!

Core Purpose: Convert any text into a URL-friendly slug (e.g., "مقالات تقنية" → "مقالات-تقنية" or "maqalat-taqniya").

Features

  • Simple & Clean - Takes text, generates slug. That's it!
  • Automatic Model Integration - Add trait, done!
  • Supports Translated Fields - Works with translation packages (optional)
  • Preserve Original Language - Keeps Arabic/Unicode characters by default
  • Unique Slugs - Automatically ensures uniqueness in database
  • Auto-regeneration - Regenerates when source field changes
  • Clean Text - Removes HTML, punctuation, special characters
  • Find by Slug - findWhereSlug() method for easy model retrieval (avoids conflicts)
  • Skip Generation - Conditionally skip slug generation with skipSlugGenerationWhen()
  • Prevent Overwrite - Protect existing slugs from being overwritten
  • Extra Scopes - Multi-tenant support with custom uniqueness scopes
  • Custom Suffix - Start suffix from custom number or use custom generator
  • Multiple Fields - Generate slug from multiple source fields
  • Callable Source - Generate slug from custom function/callable

Installation

Install via Composer

composer require shammaa/laravel-slug

That's it! The package will be installed automatically.

Publish Configuration (Optional)

php artisan vendor:publish --tag=slug-config

This will create config/slug.php in your project with default settings.

Quick Start

1. Add Trait to Your Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Shammaa\LaravelSlug\Traits\HasSlug;

class Category extends Model
{
    use HasSlug;

    // Configure slug settings
    protected $slugSourceField = 'name';           // Source field for slug
    protected $regenerateSlugOnUpdate = true;      // Regenerate on update
    protected $slugSeparator = '-';                // Separator character
    protected $slugColumn = 'slug';                 // Column name (default: 'slug')

    protected $fillable = ['name', 'slug'];
}

That's it! The package will automatically:

  • Generate slug when creating a new model
  • Regenerate slug when updating (if enabled)
  • Ensure slug is unique in database
  • Preserve original language characters by default

Example Usage

// Create a new category with Arabic text
$category = Category::create([
    'name' => 'مقالات تقنية'  // Arabic text
]);

// Slug will be automatically generated: "مقالات-تقنية" (preserved)
// Or "maqalat-taqniya" if preserve_original = false

// Update the name
$category->update([
    'name' => 'مقالات برمجية'  // New Arabic text
]);

// Slug will be automatically regenerated: "مقالات-برمجية" (preserved)

Configuration

Global Configuration

Edit config/slug.php to set defaults:

return [
    'default_separator' => '-',
    'default_column' => 'slug',
    'default_source_field' => 'name',
    'regenerate_on_update' => true,
    'preserve_original' => true,  // Keep original language characters
    'use_intl' => true,          // Use Intl for transliteration (if preserve_original = false)
];

Model-Level Configuration

You can configure slug settings in two ways:

Method 1: Using Properties (Simple)

class Article extends Model
{
    use HasSlug;

    // Source field (required)
    protected $slugSourceField = 'title';

    // Regenerate slug when source field changes (default: true)
    protected $regenerateSlugOnUpdate = true;

    // Separator character (default: '-')
    protected $slugSeparator = '-';

    // Slug column name (default: 'slug')
    protected $slugColumn = 'slug';
}

Method 2: Using SlugOptions (Recommended - More Professional)

use Shammaa\LaravelSlug\SlugOptions;

class Article extends Model
{
    use HasSlug;

    /**
     * Get the options for generating the slug.
     */
    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }
}

Benefits of SlugOptions:

  • Fluent Interface - Clean, readable code
  • Type Safety - Better IDE autocomplete
  • All Features - Access to all advanced features
  • Professional - Industry-standard pattern (like Spatie)

Full Example with All Options:

public function getSlugOptions(): SlugOptions
{
    return SlugOptions::create()
        ->generateSlugsFrom('title')                    // Source field
        ->saveSlugsTo('slug')                          // Column name
        ->separator('-')                               // Separator
        ->regenerateOnUpdate(true)                     // Regenerate on update
        ->doNotGenerateSlugsOnCreate()                 // Skip on create
        ->doNotGenerateSlugsOnUpdate()                // Skip on update
        ->skipGenerateWhen(fn($m) => $m->status === 'draft')  // Conditional skip
        ->preventOverwrite()                           // Don't overwrite existing
        ->extraScope(fn($q) => $q->where('site_id', $this->site_id))  // Multi-tenant
        ->startSlugSuffixFrom(2)                       // Start suffix from 2
        ->useSuffixOnFirstOccurrence()                 // Always add suffix
        ->usingSuffixGenerator(fn($slug, $iter) => bin2hex(random_bytes(4)))  // Custom suffix
        ->sourceIsTranslated(true)                     // Translated field
        ->sourceTranslationKey('name');               // Translation key
}

Language Preservation vs Transliteration

The package supports two modes:

Mode 1: Preserve Original (Default)

Configuration:

'preserve_original' => true

Behavior:

  • Keeps original language characters in slug
  • Works with all languages (Arabic, French, Hindi, Persian, Russian, Chinese, etc.)
  • Only converts Arabic numbers to English
  • Removes HTML tags and punctuation

Examples:

// Arabic
'مقالات تقنية' → 'مقالات-تقنية'
'أخبار الرياضة' → 'أخبار-الرياضة'

// French
'Café français' → 'Café-français'
'Été à Paris' → 'Été-à-Paris'

// Persian/Farsi
'سلام دنیا' → 'سلام-دنیا'
'ایران' → 'ایران'

// Hindi
'हिन्दी भाषा' → 'हिन्दी-भाषा'
'नमस्ते दुनिया' → 'नमस्ते-दुनिया'

// Russian
'Привет мир' → 'Привет-мир'
'Русский язык' → 'Русский-язык'

// Chinese
'你好世界' → '你好世界'
'中文' → '中文'

// Mixed languages
'مقالات Technology' → 'مقالات-Technology'
'News أخبار' → 'News-أخبار'
'Café français و مقالات' → 'Café-français-و-مقالات'

// Arabic numbers (always converted to English)
'مقال ١٢٣' → 'مقال-123'

Mode 2: Transliterate to Latin

Configuration:

'preserve_original' => false

Behavior:

  • Transliterates all characters to Latin
  • Uses PHP Intl Extension if available (best quality)
  • Falls back to manual transliteration maps if Intl not available
  • Converts to lowercase

Examples:

// Arabic - transliterated
'مقالات تقنية' → 'maqalat-taqniya'
'أخبار الرياضة' → 'akhbar-al-riyada'

// French - transliterated
'Café français' → 'cafe-francais'
'Été à Paris' → 'ete-a-paris'

// Persian/Farsi - transliterated
'سلام دنیا' → 'salam-donya'
'ایران' → 'iran'

// Hindi - transliterated (requires Intl)
'हिन्दी भाषा' → 'hindi-bhasa'
'नमस्ते दुनिया' → 'namaste-duniya'

Advanced Usage

Custom Separator

class Tag extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSeparator = '_';  // Use underscore instead of dash
}

Result:

'مقالات تقنية' → 'مقالات_تقنية'  // or 'maqalat_taqniya' if transliterated

Disable Regeneration on Update

class Post extends Model
{
    use HasSlug;

    protected $slugSourceField = 'title';
    protected $regenerateSlugOnUpdate = false;  // Keep original slug
}

Use Case: When you want to keep the original slug even if the title changes (for SEO purposes).

Custom Slug Column

class Product extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugColumn = 'url_slug';  // Use different column name
}

Translated Fields Support (Simple)

If your field is stored in a translation table (like lexi_translations), just enable it:

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;  // That's it!
}

// Works automatically - gets value from translation
$taxonomy = new Taxonomy();
$taxonomy->setTranslation('name', 'ar', 'مقالات تقنية');
$taxonomy->save();
// Slug: "مقالات-تقنية" ✅

That's it! The package automatically:

  • Detects if field is translated
  • Gets value from translation (before or after save)
  • Generates slug from that value

How It Works:

  • Uses app()->getLocale() automatically (current site language)
  • Works with any translation package (Spatie, Astrotomic, custom, etc.)
  • Detects translations before save (pending) or after save (from database)
  • Supports all common patterns: translate(), setTranslation(), getTranslation(), etc.

Manual Slug Generation

use Shammaa\LaravelSlug\Facades\Slug;

// Generate slug from string (preserves original by default)
$slug = Slug::generate('مقالات تقنية', '-');
// Result: "مقالات-تقنية" (if preserve_original = true)
// Result: "maqalat-taqniya" (if preserve_original = false)

// Generate unique slug (checks database)
$slug = Slug::generateUnique(
    'مقالات تقنية',
    'categories',
    'slug',
    '-',
    $excludeId = null  // Exclude this ID when checking
);

Regenerate Slug Manually

$category = Category::find(1);
$category->regenerateSlug();
$category->save();

Find Model by Slug

// Find a model by its slug (avoids conflicts with other packages)
$category = Category::findWhereSlug('مقالات-تقنية');

// With specific columns
$category = Category::findWhereSlug('مقالات-تقنية', ['id', 'name', 'slug']);

Note: We use findWhereSlug() instead of findBySlug() to avoid conflicts with other packages (e.g., Aliziodev\LaravelTaxonomy) that define findBySlug() with different signatures.

If your model doesn't have conflicts and you prefer findBySlug(), you can add this method to your model:

public static function findBySlug(string $slug, array|string $columns = ['*']): ?static
{
    return static::findWhereSlug($slug, $columns);
}

Skip Slug Generation Conditionally

class Article extends Model
{
    use HasSlug;

    protected $slugSourceField = 'title';
    
    protected function bootHasSlug(): void
    {
        parent::bootHasSlug();
        
        // Skip slug generation when status is 'draft'
        $this->skipSlugGenerationWhen(function ($model) {
            return $model->status === 'draft';
        });
    }
}

// Or set it dynamically
$article = new Article();
$article->title = 'My Article';
$article->status = 'draft';
$article->skipSlugGenerationWhen(fn($m) => $m->status === 'draft');
$article->save(); // No slug generated

Prevent Slug Overwrite

class Article extends Model
{
    use HasSlug;

    protected $slugSourceField = 'title';
    protected $preventSlugOverwrite = true;  // Prevent overwriting existing slug
}

$article = Article::create(['title' => 'My Article']); // slug: "my-article"
$article->title = 'Changed Title';
$article->save(); // slug stays "my-article" (not overwritten)

Extra Scope for Uniqueness (Multi-tenant Support)

class Page extends Model
{
    use HasSlug;

    protected $slugSourceField = 'title';
    
    protected function bootHasSlug(): void
    {
        parent::bootHasSlug();
        
        // Ensure slug is unique per website
        $this->setSlugExtraScope(function ($query) {
            $query->where('website_id', $this->website_id);
        });
    }
}

// Now slugs are unique per website, not globally
$page1 = Page::create(['title' => 'Home', 'website_id' => 1]); // slug: "home"
$page2 = Page::create(['title' => 'Home', 'website_id' => 2]); // slug: "home" (different website)

Custom Suffix Starting Number

class Category extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSuffixStartFrom = 2;  // Start suffix from 2 instead of 1
}

Category::create(['name' => 'مقالات']); // slug: "مقالات"
Category::create(['name' => 'مقالات']); // slug: "مقالات-2" (not "مقالات-1")
Category::create(['name' => 'مقالات']); // slug: "مقالات-3"

Use Suffix on First Occurrence

class Category extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $useSuffixOnFirstOccurrence = true;  // Always add suffix
}

Category::create(['name' => 'مقالات']); // slug: "مقالات-1" (suffix added even if unique)
Category::create(['name' => 'مقالات']); // slug: "مقالات-2"

Custom Suffix Generator

class Category extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    
    protected function bootHasSlug(): void
    {
        parent::bootHasSlug();
        
        // Generate random suffix instead of incremental numbers
        $this->setSlugSuffixGenerator(function ($baseSlug, $iteration) {
            return bin2hex(random_bytes(4)); // Random 8-character hex
        });
    }
}

Category::create(['name' => 'مقالات']); // slug: "مقالات"
Category::create(['name' => 'مقالات']); // slug: "مقالات-a3f2b1c4" (random)
Category::create(['name' => 'مقالات']); // slug: "مقالات-d5e6f7a8" (random)

Multiple Fields for Slug Generation

class Article extends Model
{
    use HasSlug;

    // Generate slug from multiple fields
    protected $slugSourceField = ['title', 'author_name'];
}

$article = Article::create([
    'title' => 'My Article',
    'author_name' => 'John Doe'
]);
// Slug: "my-article-john-doe"

Callable for Slug Generation

class Article extends Model
{
    use HasSlug;

    // Generate slug from a custom function
    protected $slugSourceField = function ($model) {
        return $model->title . ' ' . $model->id;
    };
}

// Or set it dynamically
$article = new Article();
$article->slugSourceField = function ($model) {
    return strtolower($model->title) . '-' . date('Y');
};
$article->title = 'My Article';
$article->save(); // slug: "my-article-2025"

Key Features Explained

Automatic Uniqueness

Slugs are automatically made unique by appending numbers:

// First category (preserve_original = true)
Category::create(['name' => 'مقالات']);
// Slug: "مقالات"

// Second category with same name
Category::create(['name' => 'مقالات']);
// Slug: "مقالات-1"

// Third category
Category::create(['name' => 'مقالات']);
// Slug: "مقالات-2"

// If preserve_original = false
Category::create(['name' => 'مقالات']);
// Slug: "maqalat"

Category::create(['name' => 'مقالات']);
// Slug: "maqalat-1"

HTML Tag Removal

HTML tags are automatically removed:

// preserve_original = true
'<h1>مقالات</h1>' → 'مقالات'
'<p>أخبار <strong>مهمة</strong></p>''أخبار-مهمة'

// preserve_original = false
'<h1>مقالات</h1>' → 'maqalat'
'<p>أخبار <strong>مهمة</strong></p>''akhbar-muhima'

Punctuation Handling

Punctuation marks are properly handled:

// preserve_original = true
'مقالات، تقنية!' → 'مقالات-تقنية'
'أخبار؟ مهمة.' → 'أخبار-مهمة'

// preserve_original = false
'مقالات، تقنية!' → 'maqalat-taqniya'
'أخبار؟ مهمة.' → 'akhbar-muhima'

Supported Languages

The package supports all languages when preserve_original = true. When preserve_original = false, it uses transliteration which supports:

  • Arabic (العربية)
  • English (with accented characters: é, è, ê, ë, etc.)
  • French (Français)
  • Hindi (हिन्दी) - requires Intl
  • Persian/Farsi (فارسی)
  • Russian (Русский) - requires Intl
  • Chinese (中文) - requires Intl
  • Japanese (日本語) - requires Intl
  • Korean (한국어) - requires Intl
  • Turkish (Türkçe)
  • Greek (Ελληνικά)
  • Hebrew (עברית)
  • Thai (ไทย) - requires Intl
  • Vietnamese (Tiếng Việt) - requires Intl
  • ✅ And 100+ more languages!

API Reference

SlugService Methods

generate(string $string, string $separator = '-', ?string $fallback = null): string

Generate a slug from a string.

Parameters:

  • $string - The string to convert to slug
  • $separator - Separator character (default: '-')
  • $fallback - Fallback string if result is empty (optional)

Returns: Generated slug string

Example:

Slug::generate('مقالات تقنية', '-');
// Returns: "مقالات-تقنية" (if preserve_original = true)
// Returns: "maqalat-taqniya" (if preserve_original = false)

generateUnique(string $string, string $table, string $column = 'slug', string $separator = '-', ?int $excludeId = null): string

Generate a unique slug by checking the database.

Parameters:

  • $string - The string to convert to slug
  • $table - Database table name
  • $column - Slug column name (default: 'slug')
  • $separator - Separator character (default: '-')
  • $excludeId - ID to exclude when checking (optional)

Returns: Unique slug string

Example:

Slug::generateUnique('مقالات', 'categories', 'slug', '-', $excludeId);
// Returns: "مقالات" or "مقالات-1" if exists (if preserve_original = true)
// Returns: "maqalat" or "maqalat-1" if exists (if preserve_original = false)

HasSlug Trait Methods

generateSlug(): void

Manually generate slug for the model.

$category = Category::find(1);
$category->generateSlug();
$category->save();

regenerateSlug(): self

Regenerate slug and return the model instance.

$category = Category::find(1);
$category->regenerateSlug()->save();

getSlugSourceField(): string

Get the source field name.

setSlugSourceField(string $field): self

Set the source field name.

getRegenerateSlugOnUpdate(): bool

Get regeneration setting.

setRegenerateSlugOnUpdate(bool $regenerate): self

Set regeneration setting.

getSlugSeparator(): string

Get the separator character.

setSlugSeparator(string $separator): self

Set the separator character.

getSlugColumn(): string

Get the slug column name.

setSlugColumn(string $column): self

Set the slug column name.

Complete Examples

Example 1: Basic Category Model (Preserve Original)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Shammaa\LaravelSlug\Traits\HasSlug;

class Category extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $regenerateSlugOnUpdate = true;
    protected $slugSeparator = '-';

    protected $fillable = ['name', 'slug'];
}

// Usage
$category = Category::create(['name' => 'مقالات تقنية']);
// Slug: "مقالات-تقنية"

Example 2: Article Model with Title (Transliterate)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Shammaa\LaravelSlug\Traits\HasSlug;

class Article extends Model
{
    use HasSlug;

    protected $slugSourceField = 'title';
    protected $regenerateSlugOnUpdate = false;  // Keep original slug
    protected $slugSeparator = '-';

    protected $fillable = ['title', 'slug', 'content'];
}

// Make sure preserve_original = false in config/slug.php
// Usage
$article = Article::create(['title' => 'مقالات تقنية']);
// Slug: "maqalat-taqniya"

Example 3: Product Model with Custom Column

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Shammaa\LaravelSlug\Traits\HasSlug;

class Product extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugColumn = 'url_slug';  // Custom column name
    protected $slugSeparator = '_';

    protected $fillable = ['name', 'url_slug'];
}

// Usage
$product = Product::create(['name' => 'مقالات تقنية']);
// Slug in 'url_slug' column: "مقالات_تقنية"

Example 4: Model with Translated Field (Comprehensive Support)

The package provides comprehensive support for translated fields, automatically detecting and working with multiple translation packages and patterns.

Basic Setup

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Shammaa\LaravelSlug\Traits\HasSlug;

class Taxonomy extends Model
{
    use HasSlug;

    // Configure for translated field
    protected $slugSourceField = 'name';              // Field name (used as translation key)
    protected $slugSourceIsTranslated = true;        // Enable translated field support
    protected $slugSourceTranslationKey = 'name';    // Translation key (optional, defaults to source field)
    // Locale is automatic - uses app()->getLocale() - no need to set it!
    protected $regenerateSlugOnUpdate = true;

    protected $fillable = ['slug'];
    // Note: 'name' is stored in translations table, not in taxonomies table
}

Usage Examples for Different Translation Packages

1. Laravel Translations Package (lexi_translations / translations table):

$taxonomy = new Taxonomy();
$taxonomy->setTranslation('name', 'ar', 'مقالات تقنية');
$taxonomy->save();
// Slug: "مقالات-تقنية"

2. Spatie Laravel Translatable:

$taxonomy = new Taxonomy();
$taxonomy->setTranslation('name', 'ar', 'مقالات تقنية');
$taxonomy->save();
// Slug: "مقالات-تقنية"

3. Astrotomic Laravel Translatable:

$taxonomy = new Taxonomy();
$taxonomy->translate('ar')->name = 'مقالات تقنية';
$taxonomy->save();
// Slug: "مقالات-تقنية"

4. Custom Translation Implementation:

$taxonomy = new Taxonomy();
// Works with any package that uses translate(), trans(), or getTranslation() methods
$taxonomy->translate('ar')->name = 'مقالات تقنية';
$taxonomy->save();
// Slug: "مقالات-تقنية"

How It Works (Priority Order)

The package tries multiple methods to get the translation value, in this order:

  1. Pending Translations (before save) - Checks for translations set but not yet saved
  2. Translation Methods - translate(), trans(), getTranslation()
  3. Translations Relationship - Eager loads and checks translations relationship
  4. Getter Methods - getNameAttribute() accessor
  5. Magic Properties - Direct property access $model->name
  6. Translation Tables - Direct database queries to translations, lexi_translations, etc.
  7. getTranslations() Method - For packages with this method
  8. Attributes - Fallback to model attributes

Supported Translation Packages

Laravel Translations (lexi_translations, translations table)
Spatie Laravel Translatable
Astrotomic Laravel Translatable
Custom implementations with translate(), trans(), or getTranslation() methods
Any package with translations relationship
Key-value translation tables (lexi_translations, translations, model_translations)
Column-based translation tables (laravel-translations style)

How Locale Works (Automatic)

The package automatically uses app()->getLocale() - no configuration needed!

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;
    // No slugSourceTranslationLocale needed - uses app()->getLocale() automatically!
}

// Example usage:
app()->setLocale('ar');
$taxonomy->save();  // Slug generated from Arabic translation ✅

app()->setLocale('en');
$taxonomy->save();  // Slug generated from English translation ✅

// The slug automatically adapts to the current site language!

Key Benefits:

  • No fixed locale - Always matches current site language
  • Multi-language ready - Works with any locale
  • Automatic - No manual configuration needed
  • Flexible - Changes when site language changes

Advanced: Slug Per Locale (Multiple Languages)

The package supports generating separate slugs for each language, perfect for multilingual sites.

Option 1: Separate Columns (slug_ar, slug_en, etc.)

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;
    protected $slugPerLocale = true;              // Enable per-locale slugs
    protected $slugColumnPerLocale = true;        // Use separate columns
}

// Usage
$taxonomy = new Taxonomy();
$taxonomy->setTranslation('name', 'ar', 'مقالات تقنية');
$taxonomy->setTranslation('name', 'en', 'Technical Articles');
$taxonomy->save();

// Results:
// $taxonomy->slug_ar = "مقالات-تقنية"
// $taxonomy->slug_en = "technical-articles"

Option 2: Single Column with Current Locale

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;
    protected $slugPerLocale = false;             // Single slug (default)
    protected $slugColumnPerLocale = false;       // Use single column
    // No slugPrimaryLocale needed - uses app()->getLocale() automatically!
}

// Slug will be generated from current site language (app()->getLocale())
// When locale changes, slug changes automatically
// Example:
// app()->setLocale('ar'); $taxonomy->save(); // Slug from Arabic
// app()->setLocale('en'); $taxonomy->save(); // Slug from English

Option 3: Global Configuration

// config/slug.php
return [
    'per_locale' => true,              // Enable per-locale slugs globally
    'column_per_locale' => true,       // Use separate columns
    'primary_locale' => 'ar',          // Primary locale (if column_per_locale = false)
];

Available Locales Detection:

The package automatically detects available locales from:

  1. Model's getAvailableLocales() method
  2. Model's $translatableLocales property
  3. config('app.available_locales')
  4. App locale + fallback locale

Custom Available Locales:

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;
    protected $slugPerLocale = true;
    protected $translatableLocales = ['ar', 'en', 'fr'];  // Custom locales
}

// Or implement method:
public function getAvailableLocales(): array
{
    return ['ar', 'en', 'fr'];
}

Complete Example: Multilingual Taxonomy

class Taxonomy extends Model
{
    use HasSlug;

    protected $slugSourceField = 'name';
    protected $slugSourceIsTranslated = true;
    protected $slugPerLocale = true;
    protected $slugColumnPerLocale = true;  // Use slug_ar, slug_en, etc.
    protected $translatableLocales = ['ar', 'en', 'fr'];
}

// Usage
$taxonomy = new Taxonomy();
$taxonomy->setTranslation('name', 'ar', 'مقالات تقنية');
$taxonomy->setTranslation('name', 'en', 'Technical Articles');
$taxonomy->setTranslation('name', 'fr', 'Articles Techniques');
$taxonomy->save();

// Results:
// $taxonomy->slug_ar = "مقالات-تقنية"
// $taxonomy->slug_en = "technical-articles"
// $taxonomy->slug_fr = "articles-techniques"

// Access slug for current locale
$currentSlug = $taxonomy->{app()->getLocale() === 'ar' ? 'slug_ar' : 'slug_en'};

// Or create a helper method:
public function getSlugForLocale(?string $locale = null): ?string
{
    $locale = $locale ?: app()->getLocale();
    $column = "slug_{$locale}";
    return $this->getAttribute($column);
}

When to Use Each Option:

  • slugPerLocale = false (default): Single slug from current/primary locale

    • Use when: You want one URL structure, language is handled via routes/prefixes
    • Example: /ar/category-slug or /en/category-slug (same slug, different prefix)
  • slugPerLocale = true + slugColumnPerLocale = true: Separate columns

    • Use when: Each language needs its own unique slug
    • Example: /ar/مقالات-تقنية vs /en/technical-articles (different slugs)
  • slugPerLocale = true + slugColumnPerLocale = false: Primary locale in main column

    • Use when: You want slugs for all locales but main slug from primary locale
    • Example: Main slug column has Arabic slug, other locales stored in translations

Important Notes

  • ✅ Works before save() - Detects pending translations set via setTranslation() or similar methods
  • ✅ Works after save() - Retrieves translations from database
  • Automatic detection - No need to configure package-specific settings
  • Multiple table structures - Supports key-value and column-based translation tables
  • Fallback support - If translation not found, slug generation is skipped gracefully
  • Performance optimized - Uses eager loading and caching when possible

Requirements

  • PHP >= 8.1
  • Laravel >= 9.0
  • Illuminate packages (support, database)
  • PHP Intl Extension (optional but recommended for transliteration mode)

Installing PHP Intl Extension

Ubuntu/Debian:

sudo apt-get install php-intl
sudo systemctl restart php-fpm  # or apache2/nginx

CentOS/RHEL:

sudo yum install php-intl
sudo systemctl restart php-fpm

macOS (Homebrew):

brew install php-intl

Windows: Enable the php_intl.dll extension in your php.ini file:

extension=php_intl.dll

Note:

  • The package works perfectly without Intl extension when preserve_original = true (default)
  • Intl extension is only needed for transliteration mode (preserve_original = false) with languages like Hindi, Russian, Chinese, etc.

Troubleshooting

Slug is not being generated

Problem: Slug column remains empty after creating a model.

Solutions:

  1. Make sure the slug column exists in your database table
  2. Check that $slugSourceField matches your actual field name
  3. Verify the source field has a value before saving

Slug contains HTML tags

Problem: HTML tags appear in the slug.

Solution: The package automatically removes HTML tags. If you see tags, make sure you're using the latest version.

Duplicate slugs

Problem: Multiple models have the same slug.

Solution: The package automatically handles uniqueness. If you see duplicates, check:

  1. The slug column has a unique index in database
  2. The generateUnique method is being used (automatic with trait)

Unicode characters not working

Problem: Unicode characters (Arabic, Chinese, etc.) appear as question marks or empty.

Solutions:

  1. Make sure your database uses UTF-8 encoding
  2. Set database charset to utf8mb4:
    // config/database.php
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
  3. Ensure your PHP file is saved with UTF-8 encoding

License

MIT License - feel free to use in commercial and personal projects.

Support

For issues, questions, or contributions, please open an issue on GitHub.

Author

Shadi Shammaa