المستوى المطلوب: مبتدئ
لو حفظت اسم عربي في قاعدة بيانات أو ملف، ورجعت لقيته بقى رموز زي Ø£ØÙ…د أو علامات استفهام ????، المقال ده هيخلّيك تعرف السبب بالظبط وتصلّحه في دقايق. المشكلة مش في الكيبورد ولا في السيرفر. المشكلة في حاجة اسمها الترميز (encoding).
ليه النص العربي بيتحول لرموز غريبة؟
الكمبيوتر مابيعرفش حروف. هو بيعرف بايتات، يعني أرقام. عشان يخزّن كلمة "أحمد"، بيحوّلها لمجموعة بايتات بقاعدة متفق عليها. لو خزّنت بقاعدة وقريت بقاعدة تانية، بيطلعلك كلام مكسّر اسمه mojibake.
المشكلة باختصار
أي نص بيتخزّن بايتات. والبايتات لوحدها مالهاش معنى من غير ما تعرف "اتكتبت بأي ترميز". لو الترميز اللي بتقرأ بيه مختلف عن اللي خزّنت بيه، الحروف بتتفكّك غلط. ده أشهر سبب لظهور العربي كرموز في المواقع وقواعد البيانات.
مثال يقرّب الفكرة: دفتر الشفرة
تخيّل إنك بتبعت رسالة لصاحبك بشفرة. عندكوا دفتر بيقول الرقم 1 يساوي "أ"، الرقم 2 يساوي "ب"، وهكذا. انت كتبت بالدفتر بتاعك، بس صاحبك فتح دفتر تاني أرقامه مختلفة. هيقرأ نفس الأرقام، لكن يطلعله كلام ملوش معنى. الكمبيوتر بيعمل نفس الحاجة بالظبط: "الدفتر" هو الترميز، ولو الطرفين مش بنفس الدفتر، الرسالة بتتقري غلط.
المفهوم بدقة: ASCII و Unicode و UTF-8
الخلط بين التلاتة دول هو أصل المشكلة، فخلينا نفكّهم بالترتيب:
- ASCII: أقدم دفتر. بايت واحد لكل حرف، و128 حرف بس. بيغطّي الإنجليزي والأرقام، ومافيهوش عربي خالص.
- Unicode: ده مش ترميز، ده جدول عملاق بيدّي كل حرف في كل لغات الدنيا رقم ثابت اسمه code point. مثلاً حرف الألف-همزة "أ" رقمه
U+0623. Unicode فيه أكتر من 149 ألف محرف. - UTF-8: ده الترميز اللي بيحوّل الـ code point لبايتات فعلية. طوله متغيّر: من 1 لـ 4 بايت للحرف الواحد.
القاعدة العملية في UTF-8: الحرف الإنجليزي بياخد بايت واحد، الحرف العربي غالبًا بايتين، والإيموجي لحد 4 بايت. عشان كده كلمة "أحمد" (4 حروف) بتاخد 8 بايت، مش 4.
اعملها بنفسك: كود Python بيوريك المشكلة والحل
s = "أحمد"
print(len(s)) # 4 ← عدد الحروف
print(len(s.encode("utf-8"))) # 8 ← عدد البايتات في UTF-8
print(s.encode("utf-8")) # b'\xd8\xa3\xd8\xad\xd9\x85\xd8\xaf'
# الغلطة الشائعة: قراءة بايتات UTF-8 وكأنها Latin-1
wrong = s.encode("utf-8").decode("latin-1")
print(wrong) # Ø£ØÙ…د ← ده الـ mojibake
# الإصلاح: اقرأ بنفس الترميز اللي خزّنت بيه
fixed = s.encode("utf-8").decode("utf-8")
print(fixed) # أحمد