Overview

The Shirinzad E-Commerce Platform is built with comprehensive internationalization (i18n) support, including full Persian/Farsi language support with RTL (Right-to-Left) text rendering. The platform uses ABP Framework's powerful localization system with 25+ language files.

Key Features: 25 language files, RTL support for Persian and Arabic, culture-specific date/time formatting, currency formatting for Iranian Rial (IRR), and dynamic language switching.

Supported Languages (25 Languages)

Available Language Files

The platform includes 25 language files located in: Shirinzad.Shop.Domain.Shared/Localization/Shop/

Language Culture Code File RTL Support
English (Default) en en.json No
English (UK) en-GB en-GB.json No
Arabic ar ar.json Yes
Czech cs cs.json No
German de de.json No
Spanish es es.json No
Finnish fi fi.json No
French fr fr.json No
Hindi hi hi.json No
Croatian hr hr.json No
Hungarian hu hu.json No
Icelandic is is.json No
Italian it it.json No
Dutch nl nl.json No
Polish pl-PL pl-PL.json No
Portuguese (Brazil) pt-BR pt-BR.json No
Romanian ro-RO ro-RO.json No
Russian ru ru.json No
Slovak sk sk.json No
Slovenian sl sl.json No
Swedish sv sv.json No
Turkish tr tr.json No
Vietnamese vi vi.json No
Chinese (Simplified) zh-Hans zh-Hans.json No
Chinese (Traditional) zh-Hant zh-Hant.json No
Note: To add Persian/Farsi (fa-IR) support, create a new fa.json file in the Localization/Shop directory with Persian translations.

RTL (Right-to-Left) Support

RTL Languages Configuration

The platform includes comprehensive RTL support for Persian/Farsi and Arabic languages using Bootstrap RTL CSS.

RTL Detection and CSS Loading

@{
    var currentCulture = CultureInfo.CurrentUICulture.Name;
    var isRtl = currentCulture.StartsWith("ar") || currentCulture.StartsWith("fa");
}

@if (isRtl)
{
    <link rel="stylesheet" href="~/libs/bootstrap/css/bootstrap.rtl.min.css" />
    <link rel="stylesheet" href="~/css/site.rtl.css" />
}
else
{
    <link rel="stylesheet" href="~/libs/bootstrap/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
}

RTL CSS (site.rtl.css)

/* RTL Layout Adjustments */
body {
    direction: rtl;
    text-align: right;
}

.sidebar {
    right: 0;
    left: auto;
}

.main-content {
    margin-right: 250px;
    margin-left: 0;
}

/* Flip Icons */
.icon-left {
    transform: scaleX(-1);
}

/* Form Elements */
.form-control {
    text-align: right;
}

/* Tables */
.table th,
.table td {
    text-align: right;
}

/* Breadcrumbs */
.breadcrumb-item + .breadcrumb-item::before {
    content: "\\";
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

/* Dropdown Menus */
.dropdown-menu {
    right: 0;
    left: auto;
}

Dynamic HTML Direction Attribute

<html lang="@CultureInfo.CurrentUICulture.Name" dir="@(isRtl ? "rtl" : "ltr")">

ABP Localization System

Configuration

ShopDomainSharedModule.cs

public override void ConfigureServices(ServiceConfigurationContext context)
{
    Configure<AbpVirtualFileSystemOptions>(options =>
    {
        options.FileSets.AddEmbedded<ShopDomainSharedModule>();
    });

    Configure<AbpLocalizationOptions>(options =>
    {
        options.Resources
            .Add<ShopResource>("en")
            .AddBaseTypes(typeof(AbpValidationResource))
            .AddVirtualJson("/Localization/Shop");

        options.DefaultResourceType = typeof(ShopResource);
    });

    Configure<AbpExceptionLocalizationOptions>(options =>
    {
        options.MapCodeNamespace("Shop", typeof(ShopResource));
    });
}

Localization Resource (ShopResource.cs)

using Volo.Abp.Localization;

namespace Shirinzad.Shop.Localization;

[LocalizationResourceName("Shop")]
public class ShopResource
{
}

JSON File Structure

English (en.json)

{
  "culture": "en",
  "texts": {
    "AppName": "Shop",
    "Menu:Home": "Home",
    "Welcome": "Welcome",
    "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io."
  }
}

Arabic (ar.json) - RTL Example

{
  "culture": "ar",
  "texts": {
    "AppName": "Shop",
    "Menu:Home": "الصفحة الرئيسية",
    "Welcome": "مرحباً",
    "LongWelcomeMessage": "مرحبا بكم في التطبيق. هذا مشروع بدء تشغيل يعتمد على إطار عمل ABP. لمزيد من المعلومات ، يرجى زيارة abp.io."
  }
}

Persian/Farsi (fa.json) - Example Template

{
  "culture": "fa",
  "texts": {
    "AppName": "فروشگاه",
    "Menu:Home": "صفحه اصلی",
    "Welcome": "خوش آمدید",
    "LongWelcomeMessage": "به برنامه خوش آمدید. این یک پروژه راه‌اندازی است که بر اساس چارچوب ABP ساخته شده است. برای اطلاعات بیشتر، از abp.io دیدن کنید.",

    "Menu:Products": "محصولات",
    "Menu:Categories": "دسته‌بندی‌ها",
    "Menu:Orders": "سفارشات",
    "Menu:Customers": "مشتریان",
    "Menu:Reports": "گزارش‌ها",
    "Menu:Settings": "تنظیمات",

    "Product:Name": "نام محصول",
    "Product:Price": "قیمت",
    "Product:Stock": "موجودی",
    "Product:Description": "توضیحات",
    "Product:Category": "دسته‌بندی",
    "Product:Brand": "برند",

    "Order:Number": "شماره سفارش",
    "Order:Date": "تاریخ",
    "Order:Status": "وضعیت",
    "Order:Total": "مجموع",
    "Order:Customer": "مشتری",

    "Button:Save": "ذخیره",
    "Button:Cancel": "لغو",
    "Button:Delete": "حذف",
    "Button:Edit": "ویرایش",
    "Button:Add": "افزودن",
    "Button:Search": "جستجو",

    "Message:Success": "عملیات با موفقیت انجام شد",
    "Message:Error": "خطا در انجام عملیات",
    "Message:DeleteConfirm": "آیا از حذف این مورد اطمینان دارید؟"
  }
}

Using Localization in Code

1. In Application Services (C#)

public class ProductAppService : ApplicationService
{
    public ProductAppService()
    {
        LocalizationResource = typeof(ShopResource);
    }

    public async Task<ProductDto> CreateAsync(CreateProductDto input)
    {
        // Use L to localize strings
        var successMessage = L["Product:CreatedSuccessfully"];

        // With parameters
        var welcomeMessage = L["Welcome:User", input.UserName];

        return result;
    }
}

2. In Razor Pages/Views

@using Microsoft.Extensions.Localization
@using Shirinzad.Shop.Localization
@inject IStringLocalizer<ShopResource> L

<h1>@L["Menu:Home"]</h1>
<p>@L["Welcome"]</p>

<!-- With parameters -->
<p>@L["Welcome:User", Model.UserName]</p>

<!-- In buttons -->
<button>@L["Button:Save"]</button>
<button>@L["Button:Cancel"]</button>

3. In JavaScript/TypeScript

// Using ABP's localization service
abp.localization.getResource('Shop');

// Localize a key
const welcomeMessage = abp.localization.localize('Welcome', 'Shop');

// With parameters
const userMessage = abp.localization.localize('Welcome:User', 'Shop', userName);

// In Swal alerts
Swal.fire({
    title: abp.localization.localize('Message:DeleteConfirm', 'Shop'),
    icon: 'warning',
    showCancelButton: true,
    confirmButtonText: abp.localization.localize('Button:Delete', 'Shop'),
    cancelButtonText: abp.localization.localize('Button:Cancel', 'Shop')
});

Culture-Specific Formatting

1. Date & Time Formatting

Persian Calendar (Solar Hijri)

// C# - Persian Date Formatting
var persianCalendar = new PersianCalendar();
var persianDate = $"{persianCalendar.GetYear(date)}/{persianCalendar.GetMonth(date)}/{persianCalendar.GetDayOfMonth(date)}";

// Using CultureInfo
var persianCulture = new CultureInfo("fa-IR");
var formattedDate = date.ToString("d", persianCulture); // Short date
var formattedDateTime = date.ToString("G", persianCulture); // General date/time

JavaScript Date Formatting

// Using Luxon for Persian dates
const dt = DateTime.now().setLocale('fa-IR');
const persianDate = dt.toLocaleString(DateTime.DATE_FULL);

// Using moment.js with Persian calendar
moment.locale('fa');
const persianMoment = moment().format('jYYYY/jMM/jDD'); // Jalali date

Date Picker Configuration

// Bootstrap DatePicker with Persian support
$('.datepicker').datepicker({
    language: 'fa',
    rtl: true,
    format: 'yyyy/mm/dd',
    calendarWeeks: true
});

2. Currency Formatting (Iranian Rial)

C# Currency Formatting

// Format as Iranian Rial
var persianCulture = new CultureInfo("fa-IR");
var price = 1000000m;
var formattedPrice = price.ToString("N0", persianCulture); // 1,000,000

// With currency symbol
var priceWithSymbol = $"{formattedPrice} ریال";
// Output: 1,000,000 ریال

// Alternative: Toman (1 Toman = 10 Rial)
var priceInToman = price / 10;
var formattedToman = $"{priceInToman.ToString("N0", persianCulture)} تومان";
// Output: 100,000 تومان

JavaScript Currency Formatting

// Using Intl.NumberFormat
const formatter = new Intl.NumberFormat('fa-IR', {
    style: 'decimal',
    minimumFractionDigits: 0
});

const price = 1000000;
const formattedPrice = formatter.format(price) + ' ریال';
// Output: ۱,۰۰۰,۰۰۰ ریال

// Helper function
function formatCurrency(amount, currency = 'IRR') {
    const formatted = new Intl.NumberFormat('fa-IR').format(amount);
    return currency === 'IRR' ? `${formatted} ریال` : `${formatted / 10} تومان`;
}

3. Number Formatting

Persian Digits Conversion

// C# - Convert to Persian digits
public static string ToPersianDigits(this string input)
{
    if (string.IsNullOrEmpty(input)) return input;

    var persianDigits = new[] { '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹' };
    return input.Select(c => char.IsDigit(c) ? persianDigits[c - '0'] : c)
                .Aggregate("", (current, next) => current + next);
}

// Usage
var number = "123456";
var persianNumber = number.ToPersianDigits(); // "۱۲۳۴۵۶"

JavaScript Persian Digits

function toPersianDigits(str) {
    const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
    return str.toString().replace(/\d/g, d => persianDigits[d]);
}

// Usage
const number = "123456";
const persianNumber = toPersianDigits(number); // "۱۲۳۴۵۶"

Language Switcher Implementation

Frontend Language Selector

Razor View

@using System.Globalization
@using Microsoft.AspNetCore.Localization

@{
    var requestCulture = Context.Features.Get<IRequestCultureFeature>();
    var currentCulture = requestCulture?.RequestCulture.Culture ?? CultureInfo.CurrentCulture;

    var supportedCultures = new[]
    {
        new { Code = "en", Name = "English", Flag = "🇬🇧" },
        new { Code = "fa", Name = "فارسی", Flag = "🇮🇷" },
        new { Code = "ar", Name = "العربية", Flag = "🇸🇦" },
        new { Code = "tr", Name = "Türkçe", Flag = "🇹🇷" },
        new { Code = "de", Name = "Deutsch", Flag = "🇩🇪" }
    };
}

<div class="dropdown">
    <button class="btn btn-sm btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
        @currentCulture.DisplayName
    </button>
    <ul class="dropdown-menu">
        @foreach (var culture in supportedCultures)
        {
            <li>
                <a class="dropdown-item" href="#" onclick="changeLanguage('@culture.Code')">
                    @culture.Flag @culture.Name
                </a>
            </li>
        }
    </ul>
</div>

JavaScript

function changeLanguage(culture) {
    // Set culture cookie
    document.cookie = `.AspNetCore.Culture=c=${culture}|uic=${culture}; path=/; max-age=31536000`;

    // Reload page to apply new culture
    window.location.reload();
}

// Or use AJAX to change culture without reload
function changeLanguageAjax(culture) {
    abp.utils.setCookieValue(
        '.AspNetCore.Culture',
        `c=${culture}|uic=${culture}`,
        new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000),
        '/'
    );
    window.location.reload();
}

Backend Culture Configuration

// In Program.cs or Startup.cs
app.UseRequestLocalization(options =>
{
    var supportedCultures = new[]
    {
        new CultureInfo("en"),
        new CultureInfo("fa-IR"),
        new CultureInfo("ar"),
        new CultureInfo("tr"),
        new CultureInfo("de")
    };

    options.DefaultRequestCulture = new RequestCulture("en");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    // Cookie-based culture provider
    options.RequestCultureProviders.Insert(0, new CookieRequestCultureProvider());
});

Translation Management

Adding New Translations

Step 1: Define Keys in Base Language (en.json)

{
  "culture": "en",
  "texts": {
    "Product:Name": "Product Name",
    "Product:Price": "Price",
    "Product:Stock": "Stock Quantity",
    "Product:AddedSuccessfully": "Product added successfully",
    "Product:UpdatedSuccessfully": "Product updated successfully"
  }
}

Step 2: Add Translations to Other Languages

// fa.json (Persian)
{
  "culture": "fa",
  "texts": {
    "Product:Name": "نام محصول",
    "Product:Price": "قیمت",
    "Product:Stock": "موجودی",
    "Product:AddedSuccessfully": "محصول با موفقیت اضافه شد",
    "Product:UpdatedSuccessfully": "محصول با موفقیت به‌روزرسانی شد"
  }
}

// ar.json (Arabic)
{
  "culture": "ar",
  "texts": {
    "Product:Name": "اسم المنتج",
    "Product:Price": "السعر",
    "Product:Stock": "الكمية المتاحة",
    "Product:AddedSuccessfully": "تمت إضافة المنتج بنجاح",
    "Product:UpdatedSuccessfully": "تم تحديث المنتج بنجاح"
  }
}

Step 3: Use in Code

// C#
var message = L["Product:AddedSuccessfully"];

// Razor
<label>@L["Product:Name"]</label>

// JavaScript
const message = abp.localization.localize('Product:AddedSuccessfully', 'Shop');

Localization Key Naming Conventions

Pattern Example Usage
Menu:* Menu:Home, Menu:Products Navigation menu items
Button:* Button:Save, Button:Cancel Button labels
Message:* Message:Success, Message:Error User messages and notifications
Entity:Property Product:Name, Order:Date Entity property labels
Validation:* Validation:Required, Validation:MaxLength Validation error messages
Action:* Action:Create, Action:Update Action descriptions

Best Practices

DO's

  • Use consistent key naming - Follow the pattern convention (Menu:*, Button:*, etc.)
  • Always define keys in base language first - Use English (en.json) as the base
  • Keep translations in sync - When adding new keys, update all language files
  • Use descriptive key names - Product:AddedSuccessfully not Msg1
  • Test RTL layout thoroughly - Ensure proper alignment for Persian/Arabic
  • Use culture-specific formatting - Dates, numbers, currency based on culture
  • Provide fallback translations - Missing translations should fallback to English
  • Use parameters for dynamic content - L["Welcome:User", userName]

DON'Ts

  • Don't hardcode text in views - Always use localization keys
  • Don't mix RTL and LTR in same element - Use proper Unicode direction markers
  • Don't forget to test all languages - Ensure UI doesn't break with long translations
  • Don't duplicate keys - Reuse common translations across the application
  • Don't translate technical terms unnecessarily - Keep product/brand names in original language
  • Don't forget about JavaScript strings - Client-side messages need localization too

Translation Contribution Guidelines

How to Contribute Translations

Step 1: Fork the Repository

git clone https://github.com/shirinzad/shop.git
cd shop

Step 2: Add or Update Language File

Navigate to: src/Shirinzad.Shop.Domain.Shared/Localization/Shop/

  • To add a new language: Create a new file (e.g., fa.json for Persian)
  • To update existing: Modify the appropriate JSON file

Step 3: Validate JSON Format

{
  "culture": "fa",
  "texts": {
    "Key1": "Translation 1",
    "Key2": "Translation 2"
  }
}

Step 4: Submit Pull Request

git checkout -b add-persian-translations
git add src/Shirinzad.Shop.Domain.Shared/Localization/Shop/fa.json
git commit -m "Add Persian (fa) translations"
git push origin add-persian-translations
Translation Quality: Ensure translations are contextually accurate, culturally appropriate, and follow the tone of the application.

Additional Resources

Libraries and Tools

  • Bootstrap RTL - RTL version of Bootstrap CSS framework
  • Luxon - Modern date/time library with i18n support
  • Moment.js with Jalali - Persian calendar support
  • Bootstrap DatePicker - Date picker with multi-language support
  • jQuery Timeago - Relative time formatting with Persian support
  • SweetAlert2 - Beautiful alerts with RTL support
  • Select2 - Advanced select box with RTL support

Documentation Links