تخطى إلى المحتوى الرئيسي

developer-tools

Unicode وUTF-8 وترميز الأحرف: ما يجب أن يعرفه كل مطوّر

robinhood-projectsقراءة 12 دقائق

ترميز Base64 وفك ترميزه

رمّز النص أو البيانات الثنائية بتنسيق Base64، أو فك ترميز سلاسل Base64 إلى نص عادي، فورًا في متصفحك.

استخدم الأداة ←

عارض JSON وأداة التنسيق والتحرير

اعرض JSON وحرّره ونسّقه في شجرة تفاعلية مباشرة. تحرير القيم مباشرةً، إعادة تسمية المفاتيح، إضافة/حذف العقد، دعم تعليقات JSONC، وتصدير بنقرة واحدة — كل ذلك في متصفحك.

استخدم الأداة ←

ترميز وفك ترميز URL

رمّز URLs بنسبة مئوية للاستخدام الآمن في سلاسل الاستعلام، أو فك ترميز URLs المشفرة إلى نص عادي.

استخدم الأداة ←

Unicode وUTF-8 وترميز الأحرف: ما يجب أن يعرفه كل مطوّر

تنسخ اسماً من نموذج ويب فيُحفظ في قاعدة بياناتك كـé. تفتح CSV في Excel فتجد أحرف النبرة كلها خاطئة. يلصق مستخدم إيموجي في حقل نص فتفشل عملية MySQL INSERT بأكملها برسالة خطأ مبهمة. تُضيف BOM لملف حتى يقرأه Excel بشكل صحيح، فإذا بسكريبت shell ينكسر عند السطر الأول.

هذه أخطاء ترميز أحرف. تُعدّ من أكثر المشكلات إرباكاً لأن الأعراض تبدو عشوائية — قد تُعرض السلسلة ذاتها بشكل صحيح في سياق وتتحوّل إلى حروف هالكة في سياق آخر. والسبب شبه دائم واحد: برنامجان لا يتفقان على كيفية تعيين البايتات للأحرف.

يشرح هذا الدليل هذا التعيين من الأساس: ما هي ترميزات الأحرف، وكيف تعمل Unicode وUTF-8، وكيف تشخّص أشيع الأخطاء الواقعية وتصلحها.

حقبة ما قبل Unicode: ASCII وفوضى ASCII الموسّع

كل حرف مخزَّن في حاسوب هو في نهاية المطاف رقم. ترميز الأحرف هو جدول البحث الذي يعيّن الأرقام للأحرف والعكس.

أول ترميز اعتُمد على نطاق واسع كان ASCII (American Standard Code for Information Interchange)، نُشر عام 1963. يستخدم ASCII 7 بتات مانحاً إياه 128 موقعاً (0–127). يغطي الأبجدية الإنجليزية بحالتيها الكبيرة والصغيرة، الأرقام العشرة، علامات الترقيم الشائعة، و33 حرف تحكم غير قابل للطباعة كالسطر الجديد والجدولة.

128 حرفاً كافٍ للإنجليزية. وليس قريباً من الكفاية للفرنسية أو الألمانية أو العربية أو الصينية أو اليابانية أو آلاف اللغات المكتوبة الأخرى في العالم.

مشكلة ASCII الموسَّع

البت الثامن في البايت كان متاحاً، مما يعني 128 موقعاً إضافياً (128–255). سارعت منظمات وأنظمة تشغيل مختلفة إلى ملء تلك المواقع بأساليب مختلفة:

  • Windows-1252 (يُعرف أيضاً بـ CP1252): تستخدمه Microsoft Windows في أوروبا الغربية. يضع أحرفاً كـé وñ و€ في النصف العلوي.
  • ISO-8859-1 (Latin-1): معيار دولي للغات أوروبا الغربية. يتداخل كثيراً مع Windows-1252 لكنه ليس متطابقاً — المدى 128–159 أحرف تحكم في Latin-1 لكنها أحرف قابلة للطباعة في Windows-1252.
  • ISO-8859-5: يغطي الكيريلية.
  • ISO-8859-6: يغطي العربية.
  • Shift JIS وEUC-JP وBig5: مخططات متعددة البايت مختلفة كلياً للغات شرق آسيا.

كانت النتيجة أن ملفاً أُنشئ على نظام يظهر كحروف هالكة على نظام آخر ما لم يتفق الطرفان على الترميز ذاته — ولم يكن ثمة طريقة موثوقة للإشارة إلى الترميز المستخدم. كانت البرمجيات تخمّن وتخطئ باستمرار.

هذه هي المشكلة التي وُجدت Unicode لحلّها.

Unicode: مجموعة أحرف واحدة لكل اللغات

Unicode ليست ترميزاً. هي مجموعة أحرف — جدول بحث ضخم يُعيّن رقماً فريداً لكل حرف في كل نظام كتابة مستخدَم، إضافةً إلى آلاف الرموز والإيموجي والخطوط التاريخية.

كل إدخال في ذلك الجدول يُسمى نقطة رمز (code point)، تُكتب كـU+ يتبعها رقم ست عشري. بعض الأمثلة:

الحرفنقطة الرمزالوصف
AU+0041حرف لاتيني كبير A
éU+00E9حرف لاتيني صغير e مع نبرة حادة
中U+4E2Dرمز CJK الموحَّد (zhōng)
😀U+1F600إيموجي وجه مبتسم
𝄞U+1D11Eرمز موسيقي مفتاح الجهير

يُعرِّف معيار Unicode حالياً أكثر من 140,000 حرف ويُحدَّث بانتظام.

مستويات Unicode

تتراوح نقاط رمز Unicode من U+0000 إلى U+10FFFF، مانحةً ما يزيد قليلاً على 1.1 مليون موقع ممكن. تنقسم تلك المساحة إلى 17 مستوى يحتوي كل منهما 65,536 نقطة رمز.

  • المستوى 0 — المستوى متعدد اللغات الأساسي (BMP): U+0000 إلى U+FFFF. يحتوي عملياً كل حرف شائع الاستخدام اليوم: اللاتينية واليونانية والكيريلية والعربية والعبرية وCJK وغيرها.
  • المستوى 1 — المستوى متعدد اللغات التكميلي (SMP): U+10000 إلى U+1FFFF. الإيموجي والخطوط التاريخية والتدوين الموسيقي.
  • المستويات 2–16: رموز CJK إضافية وخطوط تاريخية نادرة ومساحة غير مخصَّصة.

الإيموجي التي تكسر جداول MySQL تعيش في المستوى 1 — وهذا التفصيل مهم كما سنرى قريباً.

UTF-8: كيف تُخزَّن Unicode فعلياً

تُحدد Unicode الرقم الذي يحصل عليه كل حرف. ولا تُحدد كيفية تخزين تلك الأرقام كبايتات على القرص أو في الذاكرة — ذلك دور ترميز Unicode.

UTF-8 هو الترميز الأشيع استخداماً للـ Unicode على الويب وفي البرمجيات الحديثة. وهو متغير العرض، مما يعني أن الأحرف المختلفة تشغل أعداداً مختلفة من البايتات:

مدى نقطة الرمزالبايتات المستخدمةمَن يسكن هنا
U+0000 – U+007F1 بايتASCII (الـ 128 حرفاً كلها)
U+0080 – U+07FF2 بايتاللاتينية الموسَّعة، اليونانية، الكيريلية، العربية، العبرية
U+0800 – U+FFFF3 بايتمعظم أحرف CJK، بقية BMP
U+10000 – U+10FFFF4 بايتالإيموجي والخطوط النادرة

آلية عمل تسلسلات بايت UTF-8

UTF-8 تتزامن مع نفسها. أول بايت لأي حرف يخبرك بعدد البايتات التالية:

  • 0xxxxxxx — حرف 1 بايت (ASCII). أي ملف ASCII صالح هو UTF-8 صالح.
  • 110xxxxx 10xxxxxx — حرف 2 بايت.
  • 1110xxxx 10xxxxxx 10xxxxxx — حرف 3 بايت.
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx — حرف 4 بايت.

بايتات الاستمرار تبدأ دائماً بـ10، مما يتيح لك تحديد ما إذا كان أي بايت بداية حرف جديد أم منتصف حرف. هذا يجعل إعادة المزامنة بعد قراءة تدفق جزئي ممكنة.

مثال ملموس — ترميز é (U+00E9):

U+00E9 بالنظام الثنائي هو 11101001. يقع هذا في مدى 2 بايت. يُنتج القالب 110xxxxx 10xxxxxx القيمة 11000011 10101001 — البايتان 0xC3 0xA9 — وهو بالضبط ما تخزنه UTF-8 للحرف é.

لماذا تسود UTF-8

لـ UTF-8 ثلاث خصائص أوصلتها إلى الهيمنة:

  1. التوافق مع ASCII. أي ملف ASCII صرف هو UTF-8 صالح. ترحيل الأنظمة القائمة لم يستلزم لمس الملفات التي لا تحتوي محتوى غير ASCII.
  2. كفاءة المساحة للخطوط اللاتينية. النص الإنجليزي يستخدم بايتاً واحداً للحرف، مثل ASCII تماماً. فقط الأحرف خارج نطاق ASCII تكلّف بايتات إضافية.
  3. لا التباس في ترتيب البايتات. بنية البايت لا لبس فيها دون أي رأسية. (قارن ذلك بـ UTF-16 التي تستلزم علامة ترتيب البايتات.)

UTF-16 وUTF-32: متى ستلتقي بهما

UTF-8 ليست الترميز الوحيد لـ Unicode، وبعض البيئات تستخدم غيرها.

UTF-16 تُرمّز أحرف BMP ببايتين (وحدة رمز 16 بت واحدة) وأحرف المستويات التكميلية بأربعة بايتات (وحدتا رمز 16 بت، تُسمّيان زوجاً بديلاً). تستخدم مكوّنات Windows الداخلية وسلاسل Java وسلاسل JavaScript (على مستوى المحرك) وسلاسل .NET كلها UTF-16 داخلياً.

UTF-32 تستخدم بالضبط أربعة بايتات لكل حرف دائماً. تُهدر المساحة لكنها تجعل الوصول العشوائي بمؤشر الحرف سريعاً للغاية. بعض قواعد البيانات الداخلية تستخدمها.

علامة ترتيب البايتات (BOM)

تعاني UTF-16 من مشكلة Endianness. يمكن تخزين تسلسل بايتين بصورة Big-endian (‎0x00 0x41 للحرف A) أو Little-endian (‎0x41 0x00). المتلقي يحتاج إلى معرفة الترتيب المستخدَم.

الحل هو علامة ترتيب البايتات: نقطة الرمز U+FEFF تُوضع في بداية الملف. في UTF-16 Big-endian تظهر كالبايتين FE FF، وفي UTF-16 Little-endian كالبايتين FF FE. القارئ الذي يرى أيهما يعرف ترتيب البايتات.

لـ UTF-8 تقنياً علامة BOM أيضاً (EF BB BF) لكنها لا معنى لها في UTF-8 إذ لا يوجد التباس في ترتيب البايتات. هنا يسبّب الأمر مشكلات:

  • Excel وملفات CSV. يستخدم Excel BOM من UTF-8 إشارةً لتفسير CSV كـ UTF-8. بدونها يعود Excel إلى ترميز locale النظام (غالباً Windows-1252) محوّلاً أحرف النبرة إلى حروف هالكة. يُضيف كثير من المطوّرين BOM تحديداً لإرضاء Excel.
  • سكريبتات shell. سكريبت shell يبدأ بـ#!/bin/bash يجب أن يحتوي تلك التسلسل كأول بايتاته. إن سبق BOM بـ UTF-8 السطر الافتتاحي، يرى النواة EF BB BF 23 21 فلا يتعرف على السطر الافتتاحي، مما يُفشل السكريبت أو يشغّله بالمترجم الخاطئ.

القاعدة العملية: أضف BOM لملفات CSV المخصصة لـ Excel، ولا تضفها أبداً إلى سكريبتات shell أو ملفات JSON.

أخطاء الترميز الشائعة في الواقع

Mojibake: قراءة UTF-8 كـ Latin-1

Mojibake (文字化け) مصطلح ياباني للنص المشوَّه الناجم عن تعارض الترميز. النوع الأشيع على الويب الحديث: تُقرأ سلسلة UTF-8 كأنها Latin-1 (ISO-8859-1 أو Windows-1252).

الحرف é هو U+00E9. في UTF-8 يُخزَّن كالبايتين 0xC3 0xA9. إن قرأ برنامج هذين البايتين كـ Latin-1 رأى حرفين منفصلين: 0xC3 هو à و0xA9 هو ©. تتحوّل السلسلة café إلى café.

هذا النمط التلفي المحدد — é بدلاً من é، وè بدلاً من è، وà بدلاً من à — هو العلامة الدالة على تفسير UTF-8 كـ Latin-1. ستشهده في الغالب في:

  • قواعد البيانات التي لم يُعيَّن charset الاتصال فيها على UTF-8 عند الإدراج.
  • رسائل البريد الإلكتروني المُرسَلة عبر خوادم بريد أقدم.
  • الملفات المنقولة بين أنظمة ذات إعدادات locale غير متوافقة.

الحل هو تحديد موضع التعارض وتصحيح إعلان الترميز في تلك الطبقة — لا تُرقِّع السلسلة الفاسدة بعد وقوع الأمر.

حرف الاستبدال (U+FFFD)

حين يصادف مُفكِّك الترميز تسلسل بايتات غير صالح في الترميز المتوقَّع، عليه أن يفعل شيئاً. يوصي معيار Unicode باستبداله بحرف الاستبدال U+FFFD، يظهر كـ<0xFFFD>. إن رأيت مربعات ? أو الحرف <0xFFFD> في المخرج، فذلك يعني أن مُفكِّك الترميز صادف بايتات لم يستطع تفسيرها.

أسباب شائعة:

  • بتر تسلسل UTF-8 متعدد البايتات. إن قطعت سلسلة عند حدّ بايت يقع داخل حرف CJK مؤلف من 3 بايتات، فالتسلسل الجزئي غير صالح.
  • تمرير ملف مُرمَّز بـ Latin-1 إلى مُفكِّك UTF-8 صارم.
  • استقبال بيانات من مصدر يخلط الترميزات.

utf8 مقابل utf8mb4 في MySQL: مشكلة الإيموجي

هذا واحد من أشهر أخطاء الترميز في تطوير الويب. يملك MySQL مجموعتَي أحرف تُسمّيان utf8 وutf8mb4.

utf8 في MySQL ليس UTF-8 حقيقياً. يدعم نقاط الرمز حتى U+FFFF فحسب — المستوى متعدد اللغات الأساسي تماماً — بحد أقصى ثلاثة بايتات للحرف. الإيموجي تعيش في المستوى 1 (U+10000 وما فوق) وتستلزم أربعة بايتات. حين تحاول إدراج حرف UTF-8 رباعي البايت في عمود مُعرَّف كـutf8، يقوم MySQL بأحد أمرين: بتر القيمة صامتاً أو رمي خطأ.

utf8mb4 هو UTF-8 الحقيقي مع دعم كامل للأربعة بايتات. أُدرج تحديداً للتعامل مع الإيموجي وسائر أحرف المستوى التكميلي.

المقاربة الصحيحة: استخدم دائماً utf8mb4 للجداول وقواعد البيانات الجديدة، وعيِّن charset الاتصال وترتيب المقارنة صراحةً:

ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

وعيِّن معامل الاتصال في إعدادات تطبيقك:

charset=utf8mb4

استخدام utf8 في MySQL فخّ موروث. عامله كمهجور.

طول السلسلة مقابل عدد الأحرف في Python

كان Python 2 يخزّن السلاسل كتسلسلات بايتات افتراضياً. كانت len("café") تُعيد 5 لسلسلة UTF-8 — لأن é تشغل بايتين — لا 4. التعامل الصحيح مع متعدد البايتات استلزم حرفياً unicode صريحة u"" وضبطاً دقيقاً للترميز وفكّ الترميز.

أصلح Python 3 ذلك: str تسلسل من نقاط رمز Unicode، فـlen("café") تُعيد 4. انتقلت بيانات البايتات إلى النوع bytes مع استدعاءات .encode() و.decode() الصريحة.

لا يزال ثمة خفاء: يعدّ len() في Python 3 نقاط الرمز لا مجموعات الغراف. الحرف é يمكن تمثيله كنقطة رمز واحدة (U+00E9) أو كـe (U+0065) مع نبرة حادة جامعة (U+0301). كلاهما يُعرض بصورة متطابقة لكنهما يُنتجان قيمتَي len() مختلفتين. التطبيقات متعددة اللغات التي تعرض عدد الأحرف للمستخدمين ينبغي أن تستخدم مكتبة تجزئة Unicode لا len() المجرّدة.

حين تفحص بيانات JSON تحتوي سلاسل متعددة اللغات قد تلاحظ هذا — ما يبدو حرفاً واحداً قد يكون داخلياً نقطتَي رمز.

جدول مقارنة الترميزات

الترميزأقصى عدد أحرفبايتات لكل رمز ASCIIبايتات للإيموجيالسياق الشائع
ASCII1281غير متاحالأنظمة القديمة، رأسيات البروتوكول
Latin-1 (ISO-8859-1)2561غير متاحنصوص أوروبا الغربية القديمة
Windows-12522561غير متاحمستندات Windows القديمة
UTF-81,114,11214الويب، Linux، macOS، الواجهات البرمجية الحديثة
UTF-161,114,11224Windows، Java، .NET، مكوّنات محرك JavaScript
UTF-321,114,11244بعض مكوّنات قواعد البيانات، معالجة النصوص
MySQL utf8~65,5361معطوبأعمدة MySQL القديمة — تجنَّب
MySQL utf8mb41,114,11214MySQL — استخدم هذا

كيفية اكتشاف مشكلات الترميز وإصلاحها

الاكتشاف

في Python:

import chardet
 
with open("mystery_file.txt", "rb") as f:
    raw = f.read()
 
result = chardet.detect(raw)
print(result)  # {'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}

تستخدم مكتبة chardet التحليل الإحصائي لتخمين الترميز من أنماط البايتات. درجة الثقة صادقة — اكتشاف الترميز هيوريستي لا محدَّد.

في المتصفح: افتح DevTools وانتقل إلى علامة تبويب Network وابحث عن الاستجابة لصفحة HTML وتحقق من رأسية Content-Type. يجب أن تقرأ text/html; charset=utf-8. إن كانت غائبة أو خاطئة سيخمّن المتصفح، وغالباً بشكل غير صحيح.

في قاعدة البيانات: تحقق من مجموعة الأحرف على مستوى قاعدة البيانات والجدول والعمود:

SHOW VARIABLES LIKE 'character_set%';
SHOW CREATE TABLE posts;

إصلاح قاعدة بيانات مصابة بـ Mojibake

إن أُدرجت البيانات كـ UTF-8 لكنها خُزِّنت كـ Latin-1 (خطأ ترحيل شائع)، أحياناً يمكن استردادها:

-- اقرأ البيانات الفاسدة كثنائي ثم فسِّرها كـ UTF-8
UPDATE posts
SET body = CONVERT(CAST(CONVERT(body USING latin1) AS BINARY) USING utf8mb4)
WHERE body REGEXP '[Ã]';

هذا لا يعمل إلا إذا كانت البايتات الأصلية سليمة وكانت مجرد وسم خاطئ. إن كانت البيانات تالفة فعلاً — بايتات محذوفة أو مستبدَلة — فلا استرداد ممكن دون نسخة احتياطية.

الوقاية

  • أعلن الترميز صراحةً دائماً. لا تعتمد على الافتراضيات أبداً.
  • عيِّن <meta charset="UTF-8"> في كل مستند HTML.
  • عيِّن Content-Type: application/json; charset=utf-8 على استجابات API.
  • حدِّد charset الاتصال عند فتح اتصالات قاعدة البيانات.
  • استخدم UTF-8 في محررك وهيِّئ نظام التحكم بالإصدارات لتطبيقه على الملفات النصية.

لمهام الترميز ما وراء مجموعات الأحرف — كتحويل البيانات الثنائية إلى تمثيلات آمنة للنص — تتعامل أداة تشفير النص وفكّ تشفيره في FileCrank مع تحويل Base64 دون أي إعداد. مقال الترميز الموجود يغطي Base64 وترميز URL وكيانات HTML إن احتجت إليها. إن كنت تعمل مع ملفات إعداد وتحتاج التحقق من أن السلاسل متعددة اللغات تبدو صحيحة، يمكنك فحص بيانات JSON مباشرةً في المتصفح.

خلاصة الأمر

تبدو أخطاء ترميز الأحرف غامضة لأن وضع الفشل — أحرف مشوَّهة أو ناقصة — يظهر بعيداً عن السبب الحقيقي. لكن الآلية الكامنة بسيطة: برنامجان يستخدمان جدولَي بحث مختلفَين لتحويل البايتات إلى أحرف.

احتفظ بهذه القواعد الثلاث أمامك:

  1. استخدم UTF-8 في كل مكان دائماً ما لم يفرض نظام بعينه غير ذلك. عيِّنه صراحةً؛ لا تعتمد على الافتراضيات أبداً.
  2. في MySQL استخدم utf8mb4 لا utf8. الفارق يظهر في اللحظة التي يُرسل فيها مستخدم إيموجي.
  3. حين ترى Mojibake، ابحث عن الحدّ. ابحث عن المكان الذي تعبر فيه البايتات نظاماً لا يعرف ترميزها. أصلح الإعلان في تلك النقطة، لا الأعراض في المكان الأبعد.

فهم الآليات — نقاط الرمز وتسلسلات البايتات وإعلانات الترميز — يعني قدرتك على تحليل خطأ ترميز جديد بمنهجية لا بتخمين. في المرة القادمة التي ترى فيها é في مخرجك، ستعرف فوراً ما حدث وأين تبحث.


كل أدوات FileCrank تعمل بالكامل داخل متصفحك. لا تُرفع أي ملفات إلى خادم. إن كنت تعمل مع نص يحتوي محتوى متعدد اللغات، جرّب عارض JSON أو أداة ترميز Base64 وفكّه — كلاهما يتعامل مع Unicode بشكل صحيح دون أي إعداد.