بین‌المللی‌سازی (Internationalization یا i18n) بلاک‌های وردپرس یک ضرورت مهندسی است که مستقیماً بر دامنه دسترسی، تجربه کاربری و پایداری بلندمدت پروژه اثر می‌گذارد. هر بلاکی که با `registerBlockType` ساخته می‌شود، اگر از توابع ترجمه مانند `__()`، `_e()`، `_x()` و `_n()` به‌درستی استفاده نکند، در زبان‌های غیرانگلیسی ناقص یا غیرقابل‌استفاده خواهد بود. در اکوسیستم وردپرس که بیش از ۵۰٪ سایت‌های وب جهان را پوشش می‌دهد و بیش از ۲۰۰ زبان فعال دارد، نادیده گرفتن i18n یعنی محدود کردن بلاک به بخش کوچکی از بازار جهانی. این مقاله چارچوب کامل بین‌المللی‌سازی بلاک‌های گوتنبرگ — از Text Domain و توابع ترجمه تا تولید POT، ترجمه JSON، و مدیریت RTL — را با تمرکز بر جزئیات فنی و تجربه عملی ارائه می‌دهد.

اولین بلاکی که در مخزن رسمی منتشر کردم، در عرض یک هفته پس از انتشار، یک بازخورد منفی از یک کاربر آلمانی دریافت کرد: تمام متن‌های بلاک در ویرایشگر انگلیسی نمایش داده می‌شدند، اما در فرانت‌اند به آلمانی ترجمه می‌شدند. این تناقض عجیب، ریشه در یک اشتباه رایج داشت: استفاده از تابع `__()` در PHP بدون بارگذاری معادل آن در JavaScript. آن تجربه، اهمیت بین‌المللی‌سازی را از یک مفهوم انتزاعی به یک ضرورت عملی تبدیل کرد.

چرا بین‌المللی‌سازی بلاک‌های وردپرس حیاتی است؟

وردپرس به‌عنوان پرکاربردترین CMS (Content Management System) جهان، بیش از ۴۳٪ سایت‌های وب را پوشش می‌دهد. از این میان، کمتر از ۳۰٪ سایت‌ها به زبان انگلیسی هستند. این یعنی نزدیک به ۷۰٪ از کاربران بالقوه وردپرس، به زبان‌هایی غیر از انگلیسی محتوا تولید می‌کنند و نیازمند بلاک‌هایی هستند که در زبان مادری آن‌ها کار کند.

وقتی یک بلاک سفارشی بدون i18n منتشر می‌شود، سه محدودیت جدی ایجاد می‌کند. اول، ویرایشگر بلاک در زبان مقصد قابل استفاده نیست: کاربر آلمانی، ژاپنی یا فارسی نمی‌تواند به‌راحتی با بلاک کار کند چون تمام برچسب‌ها، راهنماها و پیام‌های خطا به انگلیسی هستند. دوم، فرانت‌اند ناقص می‌شود: بخش‌هایی از محتوای بلاک (مثل تاریخ، عدد یا متن پیش‌فرض) به انگلیسی نمایش داده می‌شوند. سوم، پروژه در مقیاس جهانی قابل عرضه نیست: یک آژانس طراحی که برای مشتریان چندزبانه کار می‌کند، نمی‌تواند از بلاکی استفاده کند که فقط انگلیسی را پشتیبانی می‌کند.

«یک بلاک بدون i18n، یک محصول نیمه‌کاره است. شما آن را برای یک بازار ساخته‌اید، اما در ذهن خود، بازار جهانی را هدف گرفته‌اید.»

از منظر معماری نرم‌افزار، i18n یک Cross-Cutting Concern است: به‌جای یک ویژگی مستقل، یک لایه عرضی است که تمام بخش‌های بلاک را تحت تأثیر قرار می‌دهد. از Metadata بلاک و برچسب‌های ویرایشگر تا متن‌های راهنما، پیام‌های خطا، فرمت اعداد، و چیدمان RTL. اگر با ساختار فایل‌های یک افزونه استاندارد وردپرس آشنا شده باشید، می‌دانید که i18n نه یک بخش جداگانه، بلکه یک الزام در تمام لایه‌هاست.

مزیت اقتصادی i18n نیز قابل توجه است. تحقیقات بازار نشان می‌دهد که کاربران با احتمال ۷۵٪ بیشتر محصولی را خریداری می‌کنند که به زبان آن‌ها ارائه شده باشد. اگر بلاک شما در مخزن رسمی منتشر شود و از i18n پشتیبانی کند، کاربران از ترجمه‌های موجود در translate.wordpress.org بهره‌مند می‌شوند. اگر از انتشار بلاک سفارشی در مخزن وردپرس آشنا شده باشید، می‌دانید که این ترجمه‌ها به‌صورت خودکار در دسترس کاربران قرار می‌گیرند.

تفاوت i18n و l10n در معماری وردپرس

دو اصطلاح مرتبط اما متفاوت در این حوزه وجود دارد: Internationalization (i18n) و Localization (l10n). i18n فرآیند آماده‌سازی کد برای پشتیبانی از چندین زبان است و توسط توسعه‌دهنده انجام می‌شود. l10n فرآیند ترجمه واقعی متن‌ها به یک زبان خاص است و توسط مترجم یا تیم ترجمه انجام می‌شود.

این تفکیک در وردپرس به شکل مشخصی پیاده‌سازی شده است: توسعه‌دهنده با استفاده از توابع ترجمه، متن‌ها را به‌عنوان «رشته‌های قابل ترجمه» علامت‌گذاری می‌کند و ابزارهایی مثل `wp i18n make-pot` این رشته‌ها را استخراج می‌کنند. سپس مترجم‌ها فایل‌های POT را به فایل‌های PO و MO برای هر زبان تبدیل می‌کنند. اگر با راهنمای توسعه افزونه وردپرس از صفر آشنا شده باشید، این فرآیند را به‌عنوان بخشی از توسعه حرفه‌ای می‌شناسید.

معیار i18n l10n
مسئول توسعه‌دهنده مترجم
زمان در حین توسعه پس از انتشار
خروجی رشته‌های علامت‌گذاری‌شده فایل‌های PO/MO/JSON
ابزارها توابع `__()`، `_e()`، `_x()` Poedit، GlotPress، translate.wordpress.org
دامنه تأثیر کد و معماری محتوا و تجربه کاربری

نکته مهم این است که i18n یک فرآیند یک‌باره نیست. هر ویژگی جدیدی که به بلاک اضافه می‌شود، باید از ابتدا با در نظر گرفتن i18n طراحی شود. اگر بعد از انتشار، متنی به بلاک اضافه کنید که i18n ندارد، آن متن در تمام ترجمه‌های موجود ناقص خواهد بود و نیاز به بازترجمه دارد. این موضوع، به‌خصوص در پروژه‌های بلندمدت، هزینه‌های پنهانی ایجاد می‌کند که با طراحی صحیح از ابتدا قابل پیشگیری است.

Text Domain: قرارداد نام‌گذاری

Text Domain یک شناسه یکتا است که وردپرس از آن برای تشخیص این استفاده می‌کند که کدام رشته‌های ترجمه متعلق به کدام افزونه یا قالب هستند. این شناسه، در تمام فراخوانی‌های توابع ترجمه به‌عنوان پارامتر دوم یا آخر ارسال می‌شود:

__( 'Hello World', 'my-custom-block' );
_e( 'Welcome', 'my-custom-block' );
_x( 'Post', 'noun', 'my-custom-block' );

نام Text Domain باید با اسلاگ پوشه افزونه در مخزن وردپرس یکسان باشد. اگر افزونه شما my-custom-block نام دارد، Text Domain نیز باید my-custom-block باشد. اگر این نام همخوانی نداشته باشد، ترجمه‌ها از translate.wordpress.org بارگذاری نمی‌شوند و کاربران نمی‌توانند بلاک شما را به زبان خودشان استفاده کنند.

Text Domain باید در سه جا تعریف شود:

۱. هدر افزونه در PHP:

/**
 * Plugin Name:       My Custom Block
 * Text Domain:       my-custom-block
 * Domain Path:       /languages
 */

۲. فایل block.json برای بلاک:

{
  "textdomain": "my-custom-block"
}

۳. توابع ترجمه در PHP و JavaScript: به‌عنوان پارامتر در تمام فراخوانی‌ها.

نکته ظریف: در JavaScript، برخلاف PHP، نیازی به ارسال Text Domain در هر فراخوانی نیست. تابع `__()` از پکیج `@wordpress/i18n` به‌طور پیش‌فرض از Text Domain استفاده می‌کند که در زمان Build تنظیم شده است. این تنظیم از طریق Webpack Plugin `@wordpress/scripts` اعمال می‌شود. اگر با ساخت بلاک سفارشی گوتنبرگ از صفر آشنا شده باشید، این تفاوت بین PHP و JavaScript را به‌عنوان یکی از نکات مهم i18n می‌شناسید.

توابع ترجمه در PHP و JavaScript

وردپرس مجموعه‌ای از توابع ترجمه فراهم می‌کند که هرکدام برای موقعیت خاصی طراحی شده‌اند. انتخاب تابع نادرست، باعث نمایش نادرست ترجمه یا از دست رفتن رشته می‌شود.

توابع اصلی در PHP

تابع کاربرد خروجی
__() بازگشت ترجمه String
_e() نمایش ترجمه Echo
_x() ترجمه با Context String
_ex() نمایش ترجمه با Context Echo
_n() جمع و مفرد String
_nx() جمع و مفرد با Context String
esc_html__() بازگشت ترجمه Escape‌شده String
esc_attr__() ترجمه برای Attribute String
esc_html_e() نمایش ترجمه Escape‌شده Echo

نکته حیاتی: هرگز ترجمه را بدون Escaping نمایش ندهید. ترکیب esc_html__() یا esc_attr__() امنیت را تضمین می‌کند. اگر با پاک‌سازی داده‌ها در کدنویسی وردپرس آشنا شده باشید، می‌دانید که این ترکیب یکی از اصول بنیادین امنیت است.

توابع ترجمه در JavaScript

در سمت JavaScript، پکیج `@wordpress/i18n` توابع مشابهی فراهم می‌کند:

import { __, _x, _n, sprintf } from '@wordpress/i18n';

const title = __( 'Hello World', 'my-custom-block' );
const label = _x( 'Post', 'noun', 'my-custom-block' );
const message = sprintf(
    _n( '%d item', '%d items', count, 'my-custom-block' ),
    count
);

در JSX، استفاده از این توابع به‌طور مستقیم امکان‌پذیر است:

import { __ } from '@wordpress/i18n';

export default function Edit( { attributes } ) {
    return (
        <div { ...useBlockProps() }>
            <label>{ __( 'Price', 'my-custom-block' ) }</label>
            <input
                type="number"
                placeholder={ __( 'Enter amount', 'my-custom-block' ) }
            />
        </div>
    );
}

نکته مهم در JavaScript: توابع ترجمه از یک Text Domain سراسری استفاده می‌کنند که در زمان Build تنظیم می‌شود. این یعنی لازم نیست در هر فراخوانی، Text Domain را مشخص کنید. اما در JSX، اگر از کامپوننت‌های قابل بازاستفاده استفاده می‌کنید، بهتر است Text Domain را صریح ارسال کنید تا وابستگی به Configuration سراسری ایجاد نشود.

تفاوت `_x()` و `__()`

تابع `_x()` برای موقعیت‌هایی است که یک کلمه مشترک، در Contextهای مختلف معانی متفاوتی دارد. مثال کلاسیک کلمه «Post» در وردپرس است:

// به‌معنای «ارسال»
_x( 'Post', 'verb', 'my-custom-block' );

// به‌معنای «نوشته»
_x( 'Post', 'noun', 'my-custom-block' );

در زبان‌هایی مثل فارسی، این تفکیک حیاتی است. اگر از `__()` استفاده کنید، مترجم نمی‌داند کدام معنا را انتخاب کند. اگر با ساخت فیلدهای سفارشی در وردپرس کار کرده باشید، می‌دانید که این نوع Context برای فیلدهای تخصصی نیز کاربرد دارد.

i18n در block.json و Metadata بلاک

فایل `block.json` منبع اصلی Metadata بلاک است و رشته‌های متنی آن نیز باید قابل ترجمه باشند. خوشبختانه، وردپرس از نسخه ۶.۲ به بعد، رشته‌های داخل `block.json` را به‌صورت خودکار استخراج می‌کند. این یعنی نیازی به ترجمه دستی Metadata نیست، اما باید Text Domain در فایل تعریف شده باشد:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "my-plugin/price-block",
  "title": "Price Block",
  "category": "widgets",
  "description": "Display a formatted price with currency.",
  "keywords": [ "price", "currency", "money" ],
  "textdomain": "my-custom-block",
  "attributes": {
    "amount": { "type": "number", "default": 0 },
    "currency": { "type": "string", "default": "USD" }
  }
}

در این فایل، فیلدهای `title`، `description`، و `keywords` به‌عنوان رشته‌های قابل ترجمه شناسایی می‌شوند. اگر با مواردی که قبل از خرید قالب وردپرس باید بررسی کنیم آشنا شده باشید، می‌دانید که این نوع شفافیت در Metadata، یکی از معیارهای حرفه‌ای بودن یک محصول است.

نکته مهم: اگر از `registerBlockType` به‌صورت مستقیم استفاده می‌کنید (بدون `block.json`)، باید این رشته‌ها را با توابع ترجمه در JavaScript علامت‌گذاری کنید:

import { __ } from '@wordpress/i18n';
import { registerBlockType } from '@wordpress/blocks';

registerBlockType( 'my-plugin/price-block', {
    title: __( 'Price Block', 'my-custom-block' ),
    description: __( 'Display a formatted price.', 'my-custom-block' ),
    category: 'widgets',
    // ...
} );

از وردپرس ۶.۲ به بعد، رویکرد block.json توصیه می‌شود چون هم ساده‌تر است و هم امکان استخراج خودکار را فراهم می‌کند.

تولید POT و مدیریت فایل‌های ترجمه

POT (Portable Object Template) یک فایل استاندارد است که تمام رشته‌های قابل ترجمه را از کد استخراج می‌کند. این فایل، نقطه شروع فرآیند ترجمه است. برای تولید POT از ابزار wp i18n make-pot استفاده می‌شود که بخشی از WP-CLI است:

wp i18n make-pot . languages/my-custom-block.pot --domain=my-custom-block --exclude=node_modules,vendor,tests

این دستور، فایل languages/my-custom-block.pot را تولید می‌کند که شامل تمام رشته‌های قابل ترجمه از PHP، JavaScript، و `block.json` است. پارامتر --exclude از اسکن پوشه‌های غیرضروری (مثل `node_modules` و `vendor`) جلوگیری می‌کند و زمان اجرا را کاهش می‌دهد.

ساختار استاندارد پوشه languages/:

languages/
├── my-custom-block.pot          # Template اصلی
├── my-custom-block-fa_IR.po     # ترجمه فارسی (منبع)
├── my-custom-block-fa_IR.mo     # ترجمه فارسی (کامپایل‌شده)
├── my-custom-block-de_DE.po     # ترجمه آلمانی (منبع)
├── my-custom-block-de_DE.mo     # ترجمه آلمانی (کامپایل‌شده)
└── my-custom-block-fa_IR.l10n.php  # Cache ترجمه (اختیاری، وردپرس ۶.۵+)

فرآیند ترجمه به این شکل است:

  1. توسعه‌دهنده POT را از کد استخراج می‌کند.
  2. مترجم با ابزاری مثل Poedit فایل PO را برای هر زبان می‌سازد.
  3. فایل PO با ابزار msgfmt یا WP-CLI به MO کامپایل می‌شود.
  4. فایل‌های MO در پوشه languages/ قرار می‌گیرند.
  5. وردپرس به‌طور خودکار فایل MO مطابق زبان سایت را بارگذاری می‌کند.

برای انتشار در مخزن رسمی، نباید فایل‌های MO را به SVN ارسال کنید. مخزن رسمی ترجمه‌ها را از translate.wordpress.org دریافت می‌کند و فایل‌های MO به‌صورت خودکار تولید می‌شوند. فقط فایل POT و PO باید در SVN باشند. اگر با انتشار بلاک سفارشی در مخزن وردپرس آشنا شده باشید، این نکته را به‌عنوان یکی از تفاوت‌های مهم بین توسعه محلی و انتشار رسمی می‌شناسید.

اسکریپت‌های npm برای i18n

در فایل package.json، اسکریپت‌های زیر برای ساده‌سازی فرآیند تعریف می‌شوند:

{
  "scripts": {
    "makepot": "wp i18n make-pot . languages/my-custom-block.pot --domain=my-custom-block --exclude=node_modules,vendor,tests",
    "makejson": "wp i18n make-json languages --no-purge",
    "build": "wp-scripts build && npm run makejson"
  }
}

دستور make-json برای تولید فایل‌های JSON است که در بخش بعدی توضیح داده می‌شود.

ترجمه‌های JSON برای JavaScript

یکی از پیچیده‌ترین بخش‌های i18n در بلاک‌های گوتنبرگ، ترجمه JavaScript است. برخلاف PHP که از فایل‌های MO استفاده می‌کند، JavaScript نیازمند فایل‌های JSON است. این تفاوت، به‌دلیل معماری متفاوت بارگذاری ترجمه در دو محیط است.

در PHP، وردپرس فایل MO را در حافظه بارگذاری می‌کند و توابع ترجمه به آن دسترسی دارند. در JavaScript، هر Script باید فایل ترجمه خودش را داشته باشد که در زمان اجرا با wp_set_script_translations() بارگذاری می‌شود:

function my_custom_block_register_scripts() {
    wp_register_script(
        'my-custom-block-editor',
        plugins_url( 'build/index.js', __FILE__ ),
        [ 'wp-blocks', 'wp-element', 'wp-i18n' ],
        '1.0.0',
        true
    );

    wp_set_script_translations(
        'my-custom-block-editor',
        'my-custom-block',
        plugin_dir_path( __FILE__ ) . 'languages'
    );
}
add_action( 'init', 'my_custom_block_register_scripts' );

تابع wp_set_script_translations() به وردپرس می‌گوید که کدام فایل JSON را برای کدام Script بارگذاری کند. این تابع، در وردپرس ۵.۰ به بعد پشتیبانی می‌شود و پیش‌نیاز ترجمه JavaScript است.

فایل‌های JSON با نام‌گذاری خاصی تولید می‌شوند:

my-custom-block-fa_IR-{hash}.json

در این نام، {hash} یک شناسه یکتا است که از MD5 مسیر فایل JavaScript استخراج می‌شود. این مکانیزم، امکان ترجمه مجزای هر Script را فراهم می‌کند. اگر با منابع ضروری برای توسعه‌دهندگان وردپرس آشنا شده باشید، می‌دانید که این جزئیات، بخشی از دانش تخصصی توسعه بلاک هستند.

برای تولید فایل‌های JSON، از دستور زیر استفاده می‌شود:

wp i18n make-json languages/ --no-purge

پارامتر --no-purge از حذف فایل‌های PO جلوگیری می‌کند. این دستور، برای هر فایل PO، یک یا چند فایل JSON تولید می‌کند.

«اگر ترجمه PHP کار می‌کند اما JavaScript نه، به احتمال زیاد فراموش کرده‌اید که wp_set_script_translations() را فراخوانی کنید.»

مشکل رایج: ترجمه ناقص در ویرایشگر

یکی از شایع‌ترین مشکلات گزارش‌شده توسط توسعه‌دهندگان بلاک، ترجمه ناقص در ویرایشگر است. برخی متن‌ها به زبان سایت ترجمه می‌شوند و برخی به انگلیسی می‌مانند. این مشکل معمولاً سه علت دارد:

علت اول: Text Domain در تابع wp_set_script_translations() با Text Domain سراسری متفاوت است.

علت دوم: فایل JSON در مسیر اشتباهی قرار گرفته یا نام‌گذاری آن با Hash نادرست است.

علت سوم: تابع wp_set_script_translations() قبل از wp_register_script() فراخوانی شده است. باید ترتیب رعایت شود: اول Script ثبت شود، سپس ترجمه متصل گردد.

پشتیبانی از RTL و چیدمان راست‌به‌چپ

پشتیبانی از RTL (Right-to-Left) یکی از چالش‌های مهم i18n است که اغلب نادیده گرفته می‌شود. زبان‌هایی مثل فارسی، عربی، عبری و اردو از چیدمان راست‌به‌چپ استفاده می‌کنند. اگر بلاک شما از RTL پشتیبانی نکند، در این زبان‌ها چیدمان به‌هم می‌ریزد و تجربه کاربری نامناسبی ایجاد می‌شود.

وردپرس یک API برای تشخیص RTL فراهم می‌کند: تابع is_rtl() در PHP و مقدار isRTL در JavaScript. همچنین فایل‌های CSS باید با پسوند -rtl.css ارائه شوند تا به‌طور خودکار در زبان‌های RTL بارگذاری شوند:

/* style.css */
.my-block {
    margin-left: 20px;
    text-align: left;
}

/* style-rtl.css */
.my-block {
    margin-right: 20px;
    text-align: right;
}

در block.json، فایل RTL به‌صورت خودکار تشخیص داده می‌شود:

{
  "style": "file:./style-index.css",
  "editorStyle": "file:./index.css"
}

وردپرس به‌طور خودکار در زبان‌های RTL، فایل style-index-rtl.css را به‌جای style-index.css بارگذاری می‌کند. برای این کار، باید فایل RTL را با همان نام و پسوند -rtl در پوشه Build قرار دهید. اگر با تایپوگرافی فارسی در طراحی وب آشنا شده باشید، می‌دانید که این جزئیات در زبان‌های RTL حیاتی هستند.

Logical Properties در CSS

رویکرد مدرن برای پشتیبانی RTL، استفاده از Logical Properties در CSS است. این ویژگی‌ها به‌جای left و right، از inline-start و inline-end استفاده می‌کنند که به‌طور خودکار با جهت متن تنظیم می‌شوند:

.my-block {
    margin-inline-start: 20px;
    padding-inline-end: 10px;
    text-align: start;
}

این رویکرد، نیاز به فایل‌های RTL جداگانه را حذف می‌کند و کد را ساده‌تر می‌سازد. با این حال، پشتیبانی مرورگرها از Logical Properties در نسخه‌های قدیمی محدود است و برای پروژه‌هایی که باید مرورگرهای قدیمی را پشتیبانی کنند، ممکن است رویکرد سنتی ترجیح داده شود. اگر با CSS مدرن: از Flexbox تا Grid آشنا شده باشید، این رویکرد را به‌عنوان بخشی از CSS نسل جدید می‌شناسید.

جمع و صیغه‌های زبانی پیچیده

جمع‌بندی در زبان‌های مختلف، پیچیدگی‌های متفاوتی دارد. در انگلیسی، جمع به دو حالت مفرد و جمع تقسیم می‌شود (1 item, 2 items). در عربی، شش حالت مختلف وجود دارد (صفر، مفرد، مثنی، جمع کوچک، جمع بزرگ). در فارسی، جمع‌بندی ساده است اما در عمل با اعداد مختلف رفتار متفاوتی دارد.

وردپرس از فرمت Plural Forms بر پایه استاندارد CLDR پشتیبانی می‌کند. تابع _n() این پیچیدگی را مدیریت می‌کند:

sprintf(
    _n( '%d item in cart', '%d items in cart', $count, 'my-custom-block' ),
    $count
);

در فایل PO، مترجم می‌تواند فرم‌های جمعی مختلف را برای زبان خود تعریف کند. مثلاً در عربی:

msgid "%d item in cart"
msgid_plural "%d items in cart"
msgstr[0] "لا توجد عناصر في السلة"
msgstr[1] "%d عنصر في السلة"
msgstr[2] "%d عنصران في السلة"
msgstr[3] "%d عناصر في السلة"
msgstr[4] "%d عنصرًا في السلة"
msgstr[5] "%d عنصر في السلة"

در JavaScript، پکیج @wordpress/i18n تابع _n() را ارائه می‌دهد که همان رفتار را در سمت کلاینت پیاده‌سازی می‌کند:

import { _n, sprintf } from '@wordpress/i18n';

const message = sprintf(
    _n( '%d item', '%d items', count, 'my-custom-block' ),
    count
);

نکته حیاتی: هرگز از ترکیب دستی عدد و کلمه خودداری کنید. همیشه از _n() و sprintf() استفاده کنید. اگر با راهنمای انتخاب تابع مناسب در وردپرس آشنا شده باشید، این اصل را به‌عنوان یک قاعده بنیادین می‌شناسید.

تاریخ، زمان، اعداد و واحد پول

یکی از پنهان‌ترین چالش‌های i18n، قالب‌بندی داده‌های غیرمتنی است: تاریخ، زمان، اعداد، و واحد پول. این داده‌ها در زبان‌های مختلف فرمت‌های متفاوتی دارند و نادیده گرفتن آن‌ها، تجربه کاربری را مختل می‌کند.

تاریخ و زمان

در PHP، از توابع وردپرس استفاده کنید:

// تاریخ با فرمت زبان سایت
echo date_i18n( get_option( 'date_format' ), strtotime( $post->post_date ) );

// تاریخ و ساعت
echo date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );

هرگز از date() مستقیم استفاده نکنید، چون فرمت تاریخ و منطقه زمانی زبان سایت را نادیده می‌گیرد. تابع date_i18n() این تنظیمات را به‌طور خودکار اعمال می‌کند.

در JavaScript، از پکیج @wordpress/date استفاده کنید:

import { dateI18n, getSettings } from '@wordpress/date';

const formatted = dateI18n(
    getSettings().formats.date,
    new Date()
);

اعداد و واحد پول

در PHP، از توابع number_format_i18n() برای اعداد و wc_price() (در WooCommerce) برای واحد پول استفاده کنید:

$price = 1234.56;
echo number_format_i18n( $price, 2 ); // خروجی: 1,234.56 یا ۱٬۲۳۴٫۵۶

// برای واحد پول (در WooCommerce)
echo wc_price( $price );

در JavaScript، از Intl.NumberFormat استفاده کنید که استاندارد مدرن وب است:

const formatter = new Intl.NumberFormat( 'fa-IR', {
    style: 'currency',
    currency: 'IRR',
} );

console.log( formatter.format( 123456 ) );

استفاده از Intl به‌جای قالب‌بندی دستی، هم دقت را افزایش می‌دهد و هم کد را ساده‌تر می‌کند. اگر با ساختاردهی داده با JSON آشنا شده باشید، می‌دانید که این رویکرد در پروژه‌های Headless نیز کاربرد دارد.

تست ترجمه و ابزارهای خودکار

تست i18n یکی از بخش‌های فراموش‌شده در توسعه بلاک است. بدون تست، نمی‌توان مطمئن بود که تمام رشته‌ها به‌درستی ترجمه می‌شوند. چند ابزار و روش برای تست i18n وجود دارد:

ابزار اول: WP-CLI i18n. دستور wp i18n make-pot نه فقط POT تولید می‌کند، بلکه هشدارهایی درباره رشته‌های مشکوک (مثل رشته‌های بدون Text Domain) نیز می‌دهد:

wp i18n make-pot . languages/my-custom-block.pot --debug

پارامتر --debug اطلاعات بیشتری درباره فرآیند استخراج نمایش می‌دهد و رشته‌های ناقص را علامت‌گذاری می‌کند.

ابزار دوم: تست با زبان شبیه‌سازی‌شده. وردپرس یک زبان به‌نام en_US با پسوند -pirate دارد که تمام رشته‌ها را به‌شکل اغراق‌آمیز تغییر می‌دهد. این زبان برای تست ترجمه ایده‌آل است چون نشان می‌دهد کدام رشته‌ها ترجمه‌پذیر هستند و کدام‌ها نیستند. اگر با رفع خطای قالب وردپرس آشنا شده باشید، می‌دانید که تست در محیط‌های مختلف، بخشی از فرآیند حرفه‌ای است.

ابزار سوم: Playwright برای تست E2E. می‌توانید یک تست Playwright بنویسید که بلاک را با زبان فارسی بارگذاری می‌کند و بررسی می‌کند که تمام متن‌ها ترجمه شده‌اند:

test( 'block should display in Persian', async ( { page } ) => {
    await page.goto( '/wp-admin/post-new.php' );
    await page.click( 'button[aria-label="Add block"]' );
    await page.fill( 'input[placeholder="Search"]', 'Price Block' );
    
    await expect( page.locator( 'button:has-text("بلاک قیمت")' ) ).toBeVisible();
} );

اگر با تست بلاک‌های وردپرس با Jest و Playwright آشنا شده باشید، این نوع تست را به‌عنوان بخشی از استراتژی کیفیت می‌شناسید.

چک‌لیست تست i18n

قبل از انتشار بلاک، این چک‌لیست را بررسی کنید:

  • آیا Text Domain در هدر افزونه، `block.json`، و تمام فراخوانی‌ها یکسان است؟
  • آیا تمام رشته‌های قابل ترجمه با توابع مناسب علامت‌گذاری شده‌اند؟
  • آیا فایل POT با wp i18n make-pot تولید شده است؟
  • آیا wp_set_script_translations() برای Script ویرایشگر فراخوانی شده است؟
  • آیا فایل‌های JSON برای JavaScript تولید شده‌اند؟
  • آیا فایل‌های RTL برای CSS وجود دارند یا از Logical Properties استفاده می‌شود؟
  • آیا از _n() برای جمع و مفرد استفاده شده است؟
  • آیا تاریخ، اعداد، و واحد پول با توابع i18n قالب‌بندی می‌شوند؟
  • آیا بلاک با زبان فارسی و یک زبان غیرلاتین (مثل ژاپنی) تست شده است؟

اشتباهات رایج در بین‌المللی‌سازی بلاک‌ها

اشتباه اول: فراموش کردن wp_set_script_translations(). این تابع، پل بین ترجمه PHP و JavaScript است. بدون آن، ترجمه‌های ویرایشگر بارگذاری نمی‌شوند و متن‌ها به انگلیسی باقی می‌مانند.

اشتباه دوم: استفاده از Text Domain نادرست. اگر Text Domain در هدر افزونه با `block.json` و توابع ترجمه یکسان نباشد، ترجمه‌ها بارگذاری نمی‌شوند. این اشتباه، شایع‌ترین دلیل ترجمه ناقص است.

اشتباه سوم: نادیده گرفتن RTL. اگر بلاک شما در زبان‌های راست‌به‌چپ چیدمان نادرست داشته باشد، کاربران این زبان‌ها نمی‌توانند از آن استفاده کنند. همیشه فایل CSS برای RTL یا از Logical Properties استفاده کنید.

اشتباه چهارم: قالب‌بندی دستی اعداد و تاریخ. استفاده از number_format() و date() به‌جای number_format_i18n() و date_i18n() باعث نمایش نادرست در زبان‌های مختلف می‌شود.

اشتباه پنجم: نادیده گرفتن جمع‌بندی. ترکیب دستی عدد و کلمه (مثل `$count . ' items'`) در زبان‌هایی مثل عربی یا روسی به نتیجه نادرست منجر می‌شود. همیشه از _n() استفاده کنید.

اشتباه ششم: ترجمه نکردن پیام‌های خطا. پیام‌های خطا، اعتبارسنجی، و موفقیت نیز باید قابل ترجمه باشند. این پیام‌ها اغلب نادیده گرفته می‌شوند چون در مسیر عادی کاربر ظاهر نمی‌شوند.

اشتباه هفتم: استفاده از متن‌های ثابت در تصاویر. اگر بلاک شما شامل تصویر با متن ثابت است، آن متن ترجمه‌پذیر نیست. راه‌حل: متن را در HTML قرار دهید نه در تصویر. اگر با اشتباهات رایج در توسعه قالب و افزونه وردپرس آشنا شده باشید، این نکته را به‌عنوان بخشی از طراحی دسترس‌پذیر می‌شناسید.

پرسش‌های پرتکرار درباره i18n بلاک‌های وردپرس

چرا متن‌های بلاک من در ویرایشگر ترجمه نمی‌شوند اما در فرانت‌اند ترجمه می‌شوند؟

این مشکل معمولاً به‌دلیل عدم فراخوانی wp_set_script_translations() است. ترجمه PHP (برای فرانت‌اند) و ترجمه JavaScript (برای ویرایشگر) دو مسیر جداگانه دارند. اگر فقط یکی از این دو پیکربندی شده باشد، بخش مربوطه ترجمه نمی‌شود. راه‌حل: تابع wp_set_script_translations() را برای Script ویرایشگر فراخوانی کنید و فایل‌های JSON را با wp i18n make-json تولید نمایید.

چگونه بلاک خود را برای زبان فارسی آماده کنم؟

چهار گام اصلی: اول، Text Domain را در هدر افزونه، `block.json` و توابع ترجمه یکسان تعریف کنید. دوم، از توابع __()، _e()، _x() و _n() در تمام متن‌ها استفاده کنید. سوم، فایل POT را تولید کنید و آن را به یک مترجم فارسی بسپارید یا خودتان ترجمه کنید. چهارم، فایل‌های RTL و CSS مناسب برای چیدمان راست‌به‌چپ ارائه دهید. اگر با تایپوگرافی فارسی در طراحی وب آشنا شده باشید، می‌دانید که این جزئیات برای تجربه کاربری فارسی حیاتی هستند.

آیا فایل‌های MO باید در SVN ارسال شوند؟

خیر. مخزن رسمی وردپرس فایل‌های ترجمه را از translate.wordpress.org دریافت می‌کند و فایل‌های MO به‌صورت خودکار در زمان نصب افزونه تولید می‌شوند. فقط فایل POT و فایل‌های PO (در صورت نیاز) باید در SVN باشند. اگر فایل‌های MO را ارسال کنید، ممکن است باعث تداخل با سیستم ترجمه رسمی شود.

آیا استفاده از Intl در JavaScript برای بلاک‌های وردپرس مجاز است؟

بله، Intl یک API استاندارد وب است که در تمام مرورگرهای مدرن پشتیبانی می‌شود. با این حال، برای ثبات با پکیج‌های وردپرس، توصیه می‌شود از @wordpress/date برای تاریخ و زمان و از Intl.NumberFormat برای اعداد و واحد پول استفاده کنید. ترکیب این دو، بهترین تجربه را فراهم می‌کند.

چگونه بلاک چندزبانه با WPML یا Polylang کار می‌کند؟

بلاک‌های سفارشی که از i18n استاندارد وردپرس پشتیبانی می‌کنند، به‌طور خودکار با WPML و Polylang کار می‌کنند. این افزونه‌ها از همان مکانیزم ترجمه وردپرس استفاده می‌کنند. اگر بلاک شما از توابع ترجمه استفاده کند، متن‌های آن در WPML و Polylang قابل ترجمه خواهند بود. تفاوت اصلی در تنظیمات هر افزونه است، نه در کد بلاک. اگر با چگونه وردپرس چندزبانه استفاده کنیم آشنا شده باشید، این موضوع را به‌عنوان بخشی از استراتژی چندزبانه می‌شناسید.

آیا ترجمه بلاک در ویرایشگر با ترجمه فرانت‌اند یکسان است؟

خیر، این دو مستقل هستند. ترجمه ویرایشگر از فایل‌های JSON و ترجمه فرانت‌اند از فایل‌های MO استفاده می‌کند. اگر یکی از این دو پیکربندی نشده باشد، متن‌ها در آن بخش ترجمه نمی‌شوند. بنابراین، باید هر دو مسیر به‌طور جداگانه تست شوند.

چگونه از ترجمه ناقص در بلاک‌های پیچیده جلوگیری کنم؟

سه راه‌حل: اول، در حین توسعه، از همان ابتدا تمام متن‌ها را با توابع ترجمه علامت‌گذاری کنید. دوم، از ابزار wp i18n make-pot --debug برای شناسایی رشته‌های ناقص استفاده کنید. سوم، با زبان en_US-pirate (زبان شبیه‌سازی‌شده وردپرس) بلاک را تست کنید تا هر رشته‌ای که ترجمه‌پذیر نیست، به‌سرعت مشخص شود.

آیا i18n برای بلاک‌های داخلی (فقط در یک سایت) ضروری است؟

اگر بلاک شما فقط در یک سایت و به یک زبان استفاده می‌شود، i18n اولویت پایین‌تری دارد. با این حال، حتی در این حالت، رعایت استانداردهای i18n توصیه می‌شود چون: اول، امکان گسترش به زبان‌های دیگر در آینده را فراهم می‌کند. دوم، کیفیت کد را افزایش می‌دهد. سوم، در پروژه‌های تیمی، باعث یکنواختی می‌شود. اگر با اصول کدنویسی تمیز در پروژه‌های وردپرس آشنا شده باشید، این رویکرد را به‌عنوان بخشی از انضباط مهندسی می‌شناسید.

نگاه نهایی به بین‌المللی‌سازی بلاک‌ها

بین‌المللی‌سازی بلاک‌های وردپرس، یک ضرورت مهندسی است که مستقیماً بر دامنه دسترسی، کیفیت تجربه کاربری، و پایداری بلندمدت پروژه اثر می‌گذارد. سه معیار کلیدی برای موفقیت در این حوزه:

۱. طراحی از ابتدا. i18n یک لایه عرضی است که باید از روز اول در معماری بلاک لحاظ شود. افزودن i18n بعد از انتشار، هزینه‌های پنهانی ایجاد می‌کند: بازترجمه، بازبینی، و بازنویسی.

۲. رعایت استانداردها. Text Domain یکسان، توابع ترجمه مناسب، فایل‌های POT و JSON به‌موقع، و پشتیبانی از RTL — این چهار عنصر، پایه i18n حرفه‌ای هستند.

۳. تست مداوم. تست i18n نه یک مرحله نهایی، بلکه یک فرآیند مستمر است. با هر ویژگی جدید، باید اطمینان حاصل کنید که رشته‌های جدید نیز قابل ترجمه هستند.

اگر در حال ساخت بلاک سفارشی هستید و قصد انتشار در مخزن رسمی را دارید، i18n یکی از الزامات بازبینی است. حتی اگر بلاک شما فقط در یک سایت استفاده می‌شود، رعایت i18n کیفیت کد را افزایش می‌دهد و آینده‌نگری را تضمین می‌کند. برای آشنایی بیشتر با توسعه بلاک و انتشار، ساخت بلاک سفارشی گوتنبرگ از صفر و انتشار بلاک سفارشی در مخزن وردپرس می‌توانند نقاط شروع خوبی باشند.

نگاه مهندسی سطح بالا

از منظر معماری نرم‌افزار، i18n یک نمونه جالب از Separation of Concerns در سطح Content است: به‌جای اینکه متن‌ها در کد Hard-code شوند، به‌عنوان منابع خارجی نگهداری می‌شوند و از طریق یک لایه انتزاعی (توابع ترجمه) بازیابی می‌گردند. این جداسازی، مزایای روشنی دارد: امکان تغییر محتوا بدون تغییر کد، امکان ترجمه به هر زبان، و امکان تست جداگانه هر زبان. اما چالش اصلی، Sync بین دو محیط PHP و JavaScript است. وردپرس با استفاده از فایل‌های MO برای PHP و JSON برای JavaScript، این Sync را به‌صورت جداگانه مدیریت می‌کند که خود یک لایه پیچیدگی است. در معماری‌های مدرن Headless، این پیچیدگی بیشتر می‌شود چون فرانت‌اند ممکن است Next.js یا Astro باشد و نه PHP. راه‌حل استاندارد، استفاده از فرمت‌های استاندارد مثل gettext و ICU MessageFormat است که در تمام زبان‌ها و پلتفرم‌ها پشتیبانی می‌شوند. اگر با اتصال وردپرس به Remix آشنا شده باشید، می‌دانید که i18n در معماری‌های Headless نیازمند یک لایه اضافه است که ترجمه را از WordPress به Frontend منتقل کند. در نهایت، i18n نه فقط یک ویژگی فنی، بلکه یک Design Decision است که بر تمام جنبه‌های بلاک — از معماری تا تجربه کاربری — اثر می‌گذارد. توسعه‌دهندگانی که این تصمیم را از ابتدا درست می‌گیرند، محصولاتی می‌سازند که در مقیاس جهانی قابل استفاده هستند.

اگر این تجربه را در یک پروژه واقعی داشته‌اید، جالب است بدانید کدام بخش از i18n بیشترین زمان را از شما گرفت. تجربه خودتان را در دیدگاه‌ها بنویسید؛ به‌خصوص اگر راه‌حل دیگری پیدا کرده‌اید که می‌تواند برای خواننده بعدی مفید باشد. 🌍

همچنین اگر می‌خواهید در مورد پیاده‌سازی عملی i18n و توسعه بلاک بیشتر بدانید، ساختار فایل‌های یک افزونه استاندارد وردپرس و چگونه قالب وردپرس را برای زبان فارسی آماده کنیم می‌توانند نقاط شروع خوبی باشند.