Arabic, Web, and UTF-8

سوف نستعرض من خلال هذه الصفحة مجموعة من الإجابات والحلول المتعلقة بالعديد من الأسئلة الشائعة والمتكررة والتي كثيرا ما تردني حول قضايا متنوعة تختص بترميز النصوص العربية باستخدام مجموعة المحارف الدولية UTF-8 في مفاصل متعددة من بيئة تطوير تطبيقات الويب باستخدام لغة PHP وقاعدة MySQL للبيانات. لذا أحببت أن أجمعها معا في صفحة واحدة تخدم كمرجع أحيل إليه السائلين من جهة، وينشر المعلومة للجميع من جهة أخرى. وأتمنى في نهاية المطاف أن يكون هذا الاجتهاد مفيدا، فإن أصبت فأعينوني بنشرها وإن أخطأت فقوموني وصححوا لي معلوماتي.

خالد الشمعة، مؤسس مشروع PHP واللغة العربي

بداية يجب أن يكون ملف برنامجك هو ذاته بتنسيق UTF-8، أبسط الطرق لتحقيق ذلك هي أن تفتح الملف باستخدام برنامج المفكرة Notepad ومن ثم تستخدم خيار حفظ باسم Save As من قائمة ملف File حيث سترى قائمة منسدلة صغيرة قرب زر الحفظ Save في أسفل صندوق الحوار الظاهر لك تسأل عن الترميز Encoding المستخدم، الآن عليك إختيار UTF-8 بدلا من ANSI ومن ثم إحفظ الملف بنفس الإسم وفي ذات المكان (مثال).

HTML & UTF-8

تحتاج المتصفحات إلى معرفة الترميز الذي تستعمله أي صفحة HTML وإلا فإنها تستخدم الترميز الافتراضي لحاسوب زائر الموقع وهو ما قد يضعك في حالة ترى بها الموقع يعمل بشكل مثالي على منصّة التطوير في حين يشكوا بعض المستخدمين من ظهور الموقع لديهم برموز غريبة غير مفهومة. يمكن تقديم هذه المعلومة من خلال معرّف meta الموضّح في المثال التالي:

HTML & UTF-8

<?php
  
// Send a raw HTTP header
  
header ('Content-Type: text/html; charset=UTF-8');
  
  
// Declare encoding META tag, it causes browser to load the UTF-8 charset before displaying the page.
  
echo '<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />';
  
  
// Right to Left issue
  
echo '<body dir="rtl">';
?>

خلال تلقين المتصفح إسم مجموعة المحارف التي عليه استخدامها، تنبّه إلى ضرورة إضافة ذلك السطر ضمن قسم head في وثيقة HTML قبل معرّف title وإلا فقدت تأثيرها على تلك الجزئيّة. كذلك لاحظ أن المصطلح Charset يستعمل هنا للدلالة على الترميز وليس على مجموعة المحارف.

Top

Apache/.htaccess & UTF-8

يمكنك أيضا أن توكل مهمة تحديد مجموعة المحارف المستخدمة إلى مخدّم الويب Apache وذلك إمّا من خلال ملف إعداداته الخاص httpd.conf على مستوى المخدّم ككل، أو من خلال ملف htaccess. على مستوى موقع مفرد أو حتى مجلّد فرعي ضمن موقع ما. يوضّح المثال التالي كيف تستطيع الطلب إلى مخدّم Apache أن يقوم بتحديد مجموعة المحارف الافتراضية على أنّها UTF-8 وهي التي سيتم إعادتها إلى المتصفح ضمن ترويسة الاستجابة ما لم يذكر خلاف ذلك صراحة.

Apache/.htaccess & UTF-8

  # This will add a charset to the Content-Type response header.
  AddDefaultCharset UTF-8

أو يمكنك تحديد مجموعة المحارف الافتراضية على أنها UTF-8 لنوع معين من الملفات فقط، المثال التالي يوضح كيفية القيام بذلك من أجل ملفات JavaScript تحديدا دون أن يؤثر على غيرها:

AddCharset UTF-8 .js

Top

php.ini & UTF-8

تقوم لغة PHP دوما بترميز مجموعة المحارف الخاصة بمخرجاتها وذلك من خلال ترويسة Content-type المرسلة مع كل إستجابة بحسب ما هو محدّد في إعداد default_charset والموجود في ملف php.ini

php.ini & UTF-8

php.ini:
default_charset = "UTF-8"
  
نفسه PHP أو في برنامج
  
<?php
    
// Sets the value of a configuration option
    // string ini_set ( string $varname , string $newvalue )
    
ini_set('default_charset','UTF-8');
?>
Top

MySQL & UTF-8

ربما تكون هذه هي إحدى النقاط الأكثر جدليّة وغموضا فيما يختص بالتعامل مع ترميز UTF-8 عند حفظ المعلومات ضمن قاعدة MySQL للبيانات، فالتوليفة الصحيحة تتألف من عدّة قطع حتى تكتمل حلقات الأحجية بالشكل الصحيح المطلوب. فبداية يجب أن تكون مجموعة المحارف المستخدمة عند إنشاء الجدول هي utf8 و COLLATE هو utf8_general_cf ومن ثم يجب إعداد مجموعة محارف مكتبة mysql في PHP على أنها utf8 باستخدام تعليمة mysql_set_charset، بعد ذلك علينا إعلام قاعدة MySQL للبيانات أن كل البيانات القادمة تستخدم ترميز UTF-8، ذات الأمر يجب أن تقوم به قاعدة البيانات حينما ترسل لنا البيانات حيث يجب أن تكون وفق نفس الترميز UTF-8 بما في ذلك الترويسة التي تحدّد ترميز مجموعة النتائج المرسلة.

إحدى أهم مزايا InnoDB مقارنة بـ MyISAM عدى عن موضوع دعمه للمناقلات (Transactions) أنه يقوم بقفل السجل الذي يقوم بتعديله فقط وليس الجدول بأكمله كما في حالة MyISAM.

MySQL & UTF-8

<?php
  
/**
   * ALTER TABLE `myTable` CHARACTER SET utf8 COLLATE utf8_general_ci;
   * 
   * There’s a generic way of doing by simply executing the MySQL query:
   * SET NAMES utf8;
   * 
   * ...and depending on which client/driver you’re using
   * there are helper functions to do this more easily instead:
   */
  
  
// With the built in mysql functions
  
mysql_set_charset('utf8'); 

  
// With MySQLi
  
$mysqli->set_charset("utf8")

  
// With PDO_MySQL (as you connect)
  
$pdo = new PDO(
      
'mysql:host=localhost;dbname=test',
      
'username',
      
'password',
      array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
  );
?>

إن كنت تستخدم الإصدار 4.0 أو ما يسبقه من قاعدة MySQL للبيانات وتحتفظ ببياناتك النصية باستخدام ترميز UTF-8 وترغب بنقل تلك البيانات إلى الإصدار 4.1 أو ما يليه من قاعدة MySQL للبيانات (أي بعد أن أضيفت إليها الخصائص الموضحة أعلاه والتي تحدد مجموعة المحارف التي يتم التعامل معها)، يجب أن تستخدم تعليمة SQL التي تقوم بتحديد نوع مجموعة المحارف المسندة للحقل المعني، لكن عليك أن تقوم بتحويل نوع ذلك الحقل بداية إلى ثنائي Binary بهدف منع المخدم من محاولة إجراء عملية تحويل ضمنية غير صريحة للبيانات في ذلك الحقل من مجموعة المحارف الافتراضية للمخدم (على سبيل المثال Windows-1252) إلى مجموعة المحارف الدولية UTF-8 وهو ما سيؤدي غالبا إلى تلف ترميز البيانات.

ALTER TABLE myTable MODIFY myColumn BINARY(255);
ALTER TABLE myTable MODIFY myColumn VARCHAR(255) CHARACTER SET utf8;

Top

Convert Character Encoding

لتحويل ترميز (encoding) نص ما من مجموعة محارف (charset) إلى أخرى يمكنك استخدام تعليمة iconv كما هو موضّح بالمثال التالي:

Convert Character Encoding

<?php
  
// Convert string to requested character encoding
  // string iconv ( string $in_charset , string $out_charset , string $str )
  
$str iconv('cp1256''utf-8'$str);
?>

إنتبه إلى أننا نستخدم الرمز cp1256 للدلالة على مجموعة المحارف Windows-1256.

Top

PHP & UTF-8 (Multibyte String Functions mb_*)

إن كافّة دوال PHP الخاصّة بالنصوص لا تتعامل مع ترميز UTF-8 بشكل صحيح خصوصاً عندما تكون تلك النصوص خليطاً من العربيّة والإنجليزيّة، وريثما يأتينا الإصدار السادس من لغة PHP والذي يعد بدعم أصيل وكامل لهذا التنسيق ضمن دوال لغة PHP كافّة. تبقى الأداة الوحيدة المتاحة لنا هي الدوال التي تقدّمها مكتبة mb حيث يوضح المثال التالي مقارنة بين ما ستحصل عليه من دالة PHP القياسيّة الخاصّة بتحديد طول سلسلة نصية ما في حال كانت مرمّزة باستخدام UTF-8، وبين الناتج الصحيح الذي ستحصل عليه عند استدعاء نسخة مكتبة mb من ذات الدالة.

لمزيد من المعلومات حول هذه المكتبة عليك الإطلاع على الرابط التالي (أو القسم الخاص بها ضمن دليل استخدام لغة PHP الذي لديك):

PHP & UTF-8 (Multibyte String Functions mb_*)

<?php
  
/**
   * Multibyte character encoding schemes and their related issues are fairly
   * complicated, and are beyond the scope of this documentation. Please refer to
   * the following URLs and other resources for further information regarding these topics. 
   */
  
  
// Set internal character encoding to UTF-8
  
mb_internal_encoding('UTF-8'); 

  
$str 'واللغة العربية PHP مشروع';

  echo 
strlen($str);             // 42
  
echo mb_strlen($str'UTF-8'); // 24

  
echo strtolower($str); // ������ ������� php ����
  
echo mb_strtolower($str); // واللغة العربية php مشروع
?>

كما تلاحظ لا يمكن حل إشكال طول السلسلة النصية بمجرّد تقسيم الطول الناتج على 2 كما قد يفعل البعض أحيانا.

Top

Email & UTF-8

الدالة البسيطة التالية تمكنك من إرسال بريد إلكتروني نصي عنوانه وموضوعه مرمّزين باستخدام مجموعة المحارف UTF-8. لكن إن رغبت بإرسال بريد إلكتروني بتنسيق HTML فما عليك سوى تعديل هذه الدالة بحيث تستبدل عبارة text/plain بعبارة text/html.

Email & UTF-8

<?php
  
/**
   * Simple UTF-8 mail sender function. This function also encode subject and 
   * plain-text message to UTF-8. If you need HTML mail sender, change the code 
   * in line 2 from text/plain to text/html, but this function is usable the 
   * most cases without any modification.
   */
        
  
function mail_utf8($to$subject '(No subject)'$message ''$header '') {
      
$_header "MIME-Version: 1.0\r\nContent-type: text/plain; charset=UTF-8\r\n";
      
mail($to'=?UTF-8?B?'.base64_encode($subject).'?='$message$_header $header);
  }
?>

يمكن أيضا إستخدام الدالة mb_send_mail من مكتبة mb المذكورة أعلاه.

Top

Regular Expressions & UTF-8

إن مجموعة تعليمات *_preg في لغة PHP والمعتمدة على مكتبة PCRE تتيح تقنية التعابير النظامية وفق ذات الصيغة والأسلوب المستخدم في لغة Perl، هذا الاستخدام في لغة PHP يدعم مجموعة المحارف العالمية UTF-8 بمجرد إضافة الخيار u/ إلى نهاية صيغة التعبير النظامي. ولسهولة بناء الأنماط العربية يمكنك الاستعانة بالمجموعات التالية:

* مجموعة الأرقام الهندية والمقصود هنا هو الأرقام ٩ ٨ ٧ ٦ ٥ ٤ ٣ ٢ ١ ٠ : [\x{0660}-\x{0669}]

* مجموعة أحرف الأبجدية العربية: [\x{0621}-\x{063A}\x{0641}-\x{064A}]

* مجموعة علامات التشكيل (الحركات) بالإضافة إلى رمز التطويل: [\x{0640}\x{064B}-\x{0652}]

* مجموعة علامات الترقيم العربية: [\x{060C}\x{060D}\x{061B}-\x{061F}\x{2E2E}\x{066A}-\x{066D}]

مجموعة علامات الترقيم الموضحة أعلاه تشمل مثلا الفاصلة العربية وعلامة الاستفهام وسواهما لكنها لا تشمل العلامات المشتركة ما بين العربية والإنجليزية مثل النقطة وعلامة التعجب والأقواس الخ...

من جهة أخرى إن استخدام رمز النقطة . في بناء النمط في التعابير النظامية للدلالة على محرف وحيد قد يسلك سلوكا غير متوقع في اللغة العربية عند وجود الحركات، حيث أن الحركة بحد ذاتها تعتبر محرفا، لذا إن كنت تقصد حرفا واحدا بغض النظر عن إمتلاكه لحركة أم لا فعليك استخدام الرمز X\ عوضا عن النقطة.

Regular Expressions & UTF-8

<?php
  
/**
   * PCRE Patterns - Pattern Modifiers: u (PCRE8)
   * This modifier turns on additional functionality of PCRE that is incompatible 
   * with Perl. Pattern strings are treated as UTF-8. This modifier is available 
   * from PHP 4.1.0 or greater on Unix and from PHP 4.2.3 on win32. UTF-8 validity 
   * of the pattern is checked since PHP 4.3.5.
   */  
  
  
$arDigits    '\x{0660}-\x{0669}'// Arabic-Indic digits
  
$arLetters   '\x{0621}-\x{063A}\x{0641}-\x{064A}'// Based on ISO 8859-6
  
$arVowels    '\x{0640}\x{064B}-\x{0652}'// Including Tatweel
  
$arPuncation '\x{060C}\x{060D}\x{061B}-\x{061F}\x{2E2E}\x{066A}-\x{066D}';
  
  
$str '١٩٧٥';
  echo 
preg_match("/^\d+$/"$str);             // No match
  
echo preg_match("/^[$arDigits]+$/u"$str); // Match
  
echo preg_match("/^[٩-٠]+$/u"$str);       // Match
?>
  • For more information about Unicode Regular Expressions please click here.
  • For more information about Unicode 6.0 Arabic character code chart click here (PDF).
Top

File & UTF-8

يستخدم رمز BOM في بداية أي دفق من البيانات (مثل محتوى ملف نصي ما أو socket) للدلالة على أن ما يليه مرمّز باستخدام مجموعة المحارف UTF-8، وتتألف علامة BOM من ثلاثة بايتات هي EFBBBF، وقد ترتقي الحاجة إلى استخدامه من إختياريّة إلى إلزاميّة في بعض بروتوكولات تدفق البيانات.

File & UTF-8

<?php
  
/**
   * Detecting UTF BOM - byte order mark
   * 
   * A byte order mark (BOM) consists of 3-byte sequence of EFBBBF at the beginning 
   * of a data stream, where it can be used as a signature defining the byte order 
   * and encoding form, primarily of unmarked plaintext files. Under some higher 
   * level protocols, use of a BOM may be mandatory (or prohibited) in the Unicode 
   * data stream defined in that protocol.      
   */
  
  
$str file_get_contents('file.utf8.csv');

  
$bom pack("CCC"0xef0xbb0xbf);

  if (
strncmp($str$bom3) == 0) {
      echo 
"BOM detected - file is UTF-8\n";
      
$str substr($str3);
  }
?>
Top

XML & UTF-8

أنت بحاجة إلى التصريح عن مجموعة المحارف المستخدمة في النصوص ضمن ملف XML:

XML & UTF-8

<?xml version="1.0"  encoding="utf-8"  ?>
<bookstore>
  <book>
    <title lang="ar">واللغة العربية PHP</title>
    <price>350</price>
  </book>
</bookstore>
Top

CSS & UTF-8

أنت بحاجة إلى التصريح عن مجموعة المحارف المستخدمة في ملف CSS أيضا في حال تضمن ذلك الملف أي محتوى على شكل نص مثلا:

CSS & UTF-8

  @charset "utf-8";

  [rel="external"]::after
  {
      content: ' رابط خارجي ';
  }
Top

JavaScript & UTF-8

إن أسهل طريقة للتأكد من من أن النصوص الواردة في ملف JavaScript برمجي يتم تضمينه في صفحتك على أنها مرمزة باستخدام مجموعة المحارف الدولية UTF-8 يتم من خلال إضافة خاصية charset إلى معرف <script> المستخدم في صفحتك.

JavaScript & UTF-8

  <script type="text/javascript" src="myscript.js" charset="utf-8" ></script>
Top

PHP & localization _() & PoEdit

PHP & localization _() & PoEdit

<?php
  
/*
  The gettext functions implement an NLS (Native Language Support) API which can 
  be used to internationalize your PHP applications. Please see the gettext 
  documentation for your system for a thorough explanation of these functions or 
  view the docs at: 
  http://www.gnu.org/software/gettext/manual/gettext.html 
  
  Poedit is cross-platform gettext catalogs (.po files) editor. It aims to provide 
  more convenient approach to editing catalogs. 
  http://www.poedit.net/
  */

  // Set language to Arabic
  // available if PHP was compiled with libintl
  
putenv('LANG=ar');
  
setlocale(LC_MESSAGES'ar.UTF-8');
  
  
$domain 'default';
  
  
// Specify location of translation tables
  
bindtextdomain($domain"./locale");
  
  
// Choose domain
  
textdomain($domain);

  
// Specify the character encoding
  
bind_textdomain_codeset($domain'UTF-8'); 
  
  
/*
  Gettext works by expecting a locale directory where all of the translated 
  strings are kept, in the following structure:
   
  locale
     |__ar
     |   |__LC_MESSAGES
     |           |__ default.po
     |           |__ default.mo
     |__en
         |__LC_MESSAGES
                 |__ default.po
                 |__ default.mo
  */
  
  // Print a test message
  
echo gettext("login") . "<br />\r\n";
  
  
// Or use the alias _() for gettext()
  
echo _("logout") . "<br />\r\n";
?>

لمعرفة اللغة الإفتراضية لمتصفح الزائر يمكنك استخدام أول حرفين من السلسلة النصية ضمن المتحول:

$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); // ar, en, etc...

Top

The Locale class provided in the intl library (PHP 5.3)

مكتبة intl في الإصدار 5.3 من لغة PHP أتت كتأطير لمكتبة International Components for Unicode - ICU الشهيرة والتي تستخدم لتوطين العديد من التطبيقات وتوفير دعم ملائم لمجموعة المحارف العالمية

The Locale class provided in the intl library

<?php
    
// Return name of language used for "en" locale in Arabic (ar)
    
echo Locale::getDisplayLanguage('en''ar''<br />'); // الانجليزية

    // Display name of Portuguese as used in Brazil in different languages
    
echo Locale::getDisplayName('ar_SY''ar'), '<br />';
    echo 
Locale::getDisplayName('ar_SY''en'), '<br />';
    echo 
Locale::getDisplayName('ar_SY''fr'), '<br />';
    echo 
Locale::getDisplayName('ar_SY''de'), '<br />';
    echo 
Locale::getDisplayName('ar_SY''ru'), '<br />';
    
    
// Return preferred locale set in user's browser
    
echo 'Preferred locale from browser: ',
         
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);  
?>
  • For more information about PHP Internationalization Functions: click here.

لاحظ أن التعريب المستخدم ليس مثاليا، فالإنجليزية تظهر بهمزة وصل بدل همزة القطع.

Top