الإيموجي للمطورين
معظم أخطاء الإيموجي تنبع من تعارض واحد: الكود يعدّ شيئًا مختلفًا عمّا يراه القارئ. إيموجي العائلة صورة واحدة، لكنه سبع نقاط ترميز (code points) و11 حرفًا في JavaScript و25 بايت. شغّلنا التعابير النمطية والمقسِّمات وفحوص الطول الشائعة على جميع الإيموجي البالغ عددها 3,963 في قائمة Unicode الرسمية، وننشر في هذه الصفحة ما تطابقه فعلًا، لتختار الطريقة المناسبة لحالتك قبل أن يخبرك تقرير خطأ بالطريقة التي اخترتها.
إيموجي واحد، وطول مختلف في كل لغة
كل خلية هي ما يُرجعه ذلك التعبير للإيموجي في رأس العمود. تنقسم النتائج إلى أربع فئات: وحدات UTF-16، ونقاط الترميز، وبايتات UTF-8، والوحدات الكتابية (graphemes، أي ما يعدّه الإنسان). يُحسب عمود الوحدات الكتابية بواسطة Intl.Segmenter على هذا الخادم، لذا قد تُرجع بيئة تشغيل ذات بيانات Unicode أقدم قيمًا أكبر لأحدث الإيموجي.
| التعبير | ما يعدّه | 😀 | ❤️ | 👍🏽 | 🇯🇵 | 👩💻 | 👨👩👧👦 | 🧑🏻❤️💋🧑🏼 |
|---|---|---|---|---|---|---|---|---|
JavaScripts.length | وحدات UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
JavaScript[...s].length | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
JavaScriptIntl.Segmenter | وحدات كتابية | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Pythonlen(s) | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Pythonlen(s.encode('utf-8')) | بايتات UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Javas.length() | وحدات UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
Javas.codePointCount(0, s.length()) | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
C#s.Length | وحدات UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
C# (.NET 5+)new StringInfo(s).LengthInTextElements | وحدات كتابية | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Golen(s) | بايتات UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Goutf8.RuneCountInString(s) | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rusts.len() | بايتات UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
Rusts.chars().count() | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Swifts.count | وحدات كتابية | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
Swifts.unicodeScalars.count | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rubys.length | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
Rubys.grapheme_clusters.length | وحدات كتابية | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
PHPstrlen($s) | بايتات UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
PHPmb_strlen($s) | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
PHPgrapheme_strlen($s) | وحدات كتابية | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
MySQLCHAR_LENGTH(s) | نقاط ترميز | 1 | 2 | 2 | 2 | 3 | 7 | 10 |
MySQLLENGTH(s) | بايتات UTF-8 | 4 | 6 | 8 | 8 | 11 | 25 | 35 |
SQL ServerLEN(s) on nvarchar | وحدات UTF-16 | 2 | 2 | 4 | 4 | 5 | 11 | 15 |
ما يفعله الاقتطاع الساذج بالإيموجي
هذه نتيجة تطبيق s.slice(0, n) على العائلة 👨👩👧👦 لكل قيمة من n. معظم عمليات القطع لا تفشل بشكل واضح، بل تُرجع عائلة مختلفة أصغر، وهكذا يصل هذا النوع من الأخطاء إلى بيئة الإنتاج دون أن يلاحظه أحد.
| القطع عند n | ما يتبقى | نقاط الترميز المتبقية |
|---|---|---|
| s.slice(0, 1) | �نصف زوج بديل (يظهر بشكل �) | 0 |
| s.slice(0, 2) | 👨 | 1 |
| s.slice(0, 3) | 👨 | 2 |
| s.slice(0, 4) | 👨�نصف زوج بديل (يظهر بشكل �) | 2 |
| s.slice(0, 5) | 👨👩 | 3 |
| s.slice(0, 6) | 👨👩 | 4 |
| s.slice(0, 7) | 👨👩�نصف زوج بديل (يظهر بشكل �) | 4 |
| s.slice(0, 8) | 👨👩👧 | 5 |
| s.slice(0, 9) | 👨👩👧 | 6 |
| s.slice(0, 10) | 👨👩👧�نصف زوج بديل (يظهر بشكل �) | 6 |
| s.slice(0, 11) | 👨👩👧👦 | 7 |
العلم الذي يغيّر الدولة
تتزاوج حروف المؤشرات الإقليمية بدءًا من أول السلسلة النصية. احذف نقطة ترميز واحدة من بداية 🇺🇸🇯🇵 فتتزاوج الحروف المتبقية بشكل مختلف: 🇸🇯🇵. لا خطأ، مجرد دولة أخرى.
🇺🇸🇯🇵 US + JP → 🇸🇯 SJ (Svalbard & Jan Mayen) + 🇵
أي تعبير نمطي للإيموجي يطابق ماذا
اختُبر بتاريخ 2026-10-20 على جميع الإيموجي fully-qualified البالغ عددها 3,963، باستخدام Node 25.2.1 الذي يتضمن بيانات Unicode 17.0. لا يُحتسب النمط مطابقًا إلا إذا طابق الإيموجي كاملًا.
| النمط | المطابقات | ما يُفلته |
|---|---|---|
| /^\p{RGI_Emoji}$/v | 3,944 / 3,963 | كل إيموجي أحدث من بيانات Unicode في المحرّك: Emoji 18.0 (19) |
| Intl.Segmenter | 3,963 / 3,963 | لا شيء على بيئة التشغيل هذه |
| /\p{Extended_Pictographic}/u | 3,692 / 3,963 | 259 علمًا و12 من أزرار المفاتيح، لا تحتوي أي نقطة ترميز تصويرية على الإطلاق |
| /^\p{Extended_Pictographic}$/u | 1,193 / 3,963 | كل ما يتكوّن من أكثر من نقطة ترميز: درجات لون البشرة، والأعلام، وأزرار المفاتيح (keycaps)، وتسلسلات ZWJ، والإيموجي ذات محدد النمط |
والنمط الذي يجب تجنّبه: \p{Emoji} يطابق أيضًا # * 0 1 2 3 4 5 6 7 8 9 © ®. إذا استخدمته لحذف الإيموجي فستحذف كل رقم في النص.
حذف الإيموجي: ما تتركه كل طريقة وراءها
بدأ السطران أدناه بالنص نفسه. البقايا غير مرئية في معظم الخطوط، وهذا بالضبط سبب المشكلات التي تسببها لاحقًا: في البحث، وإزالة التكرار، وحدود الطول.
Great 👍🏽 job ❤️ 👩💻 🇯🇵 #️⃣1
| الطريقة | النتيجة | نقاط الترميز المتبقية |
|---|---|---|
| حذف \p{Extended_Pictographic} | Great 🏽 job ️ 🇯🇵 #️⃣1 | U+1F3FD U+FE0F U+200D U+1F1EF U+1F1F5 U+FE0F U+20E3 |
| حذف \p{RGI_Emoji} | Great job 1 | لا شيء |
كم تشغل الإيموجي في قاعدة البيانات
عدد بايتات UTF-8 لكل إيموجي عبر جميع الإيموجي البالغ عددها 3,963. 30% منها تتسع في أربعة بايتات أو أقل؛ وأطولها يحتاج إلى 35. إذا كان العمود محدودًا بالبايتات لا بالأحرف، فحدّد حجمه بناءً على هذا الجدول، لا على شكل النص.
| بايتات UTF-8 | الإيموجي التي تتسع | النسبة |
|---|---|---|
| ≤ 4 | 1,193 | 30% |
| ≤ 8 | 2,346 | 59% |
| ≤ 11 | 2,424 | 61% |
| ≤ 17 | 3,424 | 86% |
| ≤ 28 | 3,868 | 98% |
| ≤ 35 | 3,963 | 100% |
🧑🏻❤️💋🧑🏼 U+1F9D1 U+1F3FB U+200D U+2764 U+FE0F U+200D U+1F48B U+200D U+1F9D1 U+1F3FC
اختر طريقة العدّ التي تطابق الحد الذي تفرضه
لا يوجد طول صحيح واحد، بل الطول الذي يطابق القاعدة التي تفرضها. عمود قاعدة البيانات المحدود بالبايتات يحتاج إلى بايتات UTF-8. وحقل النص الذي يعد المستخدم بخمسين حرفًا يحتاج إلى الوحدات الكتابية، وإلا فسيُقال لمن يكتب ثلاثة أعلام إنه استخدم 12 حرفًا. يشذّ SQL Server عن القاعدة: فـ nvarchar(n) يعدّ وحدات UTF-16، لذا يشغل 😀 حرفين من nvarchar لكنه يشغل حرفًا واحدًا من varchar في MySQL أو PostgreSQL، لأنهما يعدّان نقاط الترميز. حدّد أيّ الأربعة تقصد، واكتبه في اسم المتغير، وسيأتي الباقي تلقائيًا.
اقتطع عند حدود الوحدات الكتابية، لا عند الفهارس
أي قطع بالفهرس قد يقع في منتصف إيموجي. مع UTF-16 تكون أسوأ حالة نصف زوج بديل، تعرضه معظم محركات العرض كمربع بعلامة استفهام، وترفضه بعض مُرمِّزات JSON مباشرة. ومع نقاط الترميز لا ينكسر شيء ظاهريًا، وهذا أسوأ: تفقد العائلة طفلًا، أو تنفصل درجة لون البشرة وتظهر كمربع بني، أو يتحول العلم إلى حرفين. الحل هو التقسيم أولًا ثم قطع عدد صحيح من الوحدات الكتابية. أصبح Intl.Segmenter متاحًا في كل المتصفحات الرئيسية منذ أن أضافه Firefox 125 في أبريل 2024، وفي Node منذ مدة أطول، فلم يعد هناك مبرر لكتابة مقسِّم يدوي.
مقتطفات جاهزة للنسخ
قصيرة، ودون اعتماديات خارجية حيثما تسمح اللغة. كل واحدة تعالج حالة من الجداول أعلاه.
عدّ ما يراه القارئ (JavaScript)
const seg = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
const visibleLength = (s) => [...seg.segment(s)].length;
visibleLength('👨👩👧👦 ok'); // 4, while '👨👩👧👦 ok'.length is 14الاقتطاع دون تقسيم إيموجي (JavaScript)
function truncate(s, max, ellipsis = '…') {
const parts = [...seg.segment(s)].map((x) => x.segment);
return parts.length <= max ? s : parts.slice(0, max - 1).join('') + ellipsis;
}العثور على كل الإيموجي، بما فيها الأعلام وأزرار المفاتيح (JavaScript)
// ES2024 'v' flag: Chrome 112, Safari 17, Firefox 116, Node 20
const emoji = /\p{RGI_Emoji}/gv;
'Go 🇯🇵 #️⃣1 👍🏽'.match(emoji); // ['🇯🇵', '#️⃣', '👍🏽']الشيء نفسه في Python، باستخدام وحدة regex
import regex # pip install regex; the built-in re has no \X
graphemes = regex.findall(r'\X', '👨👩👧👦 ok')
len(graphemes) # 4, while len('👨👩👧👦 ok') is 10أعمدة تقبل كل الإيموجي
-- MySQL: utf8mb4 on the column AND the connection
CREATE TABLE posts (body VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci);
SET NAMES utf8mb4;
-- PostgreSQL: any UTF8 database already stores every emoji
-- SQL Server: use nvarchar, and remember LEN() counts UTF-16 unitsلماذا لا يكتمل أي تعبير نمطي أبدًا
كل خاصية إيموجي في محرّك التعابير النمطية تأتي من بيانات Unicode المضمّنة في ذلك المحرّك، لذا فالنمط لا يكون أحدث من بيئة التشغيل التي تنفّذه. اختبارنا يثبت ذلك: طابق \p{RGI_Emoji} 3,944 من أصل 3,963، لأن أحدث 19 تسلسلات ليست بعد في جداول Unicode 17.0 الخاصة بـ Node 25.2.1. الكود نفسه سيطابقها بعد ترقية بيئة التشغيل، بينما سيظل المستخدم على متصفح أقدم يُفلتها. إذا كان على الفحص قبول الإيموجي الجديدة منذ يوم صدورها، فطابق بالبنية (Extended_Pictographic مع المعدِّلات والوصلات والمحددات) أو بالوحدات الكتابية، ولا تستخدم الخاصية إلا حيث لا يضر السلبي الكاذب.
يمكن تخزين الإيموجي نفسه بثلاث طرق
تدرج Unicode 3,963 إيموجي fully-qualified، إضافة إلى 1,029 صيغة minimally-qualified و243 صيغة unqualified للصور نفسها، ومعظمها نسخ بلا محدد النمط U+FE0F. يبدو ❤ و❤️ متطابقين في كثير من التطبيقات لكنهما سلسلتان مختلفتان، لذا يعاملهما فحص التفرّد أو عدّاد التفاعلات أو فهرس البحث كشيئين منفصلين. ولا تساعد تسوية Unicode هنا: فـ NFC وNFKC لا تمسّان محددات التنويع. إذا كنت تقارن الإيموجي أو تزيل تكرارها، فاعتمد صيغة واحدة (fully-qualified هي الخيار الآمن) وحوّل المدخلات إليها باستخدام قائمة التأهيل، بدلًا من حذف كل U+FE0F، لأن ذلك سيكسر أيضًا أزرار المفاتيح مثل 1️⃣.
محوّل الإيموجي إلى Unicodeجميع تسلسلات ZWJ مع أحجامهاعدّاد أحرف يعدّ الإيموجي بشكل صحيحمسرد مصطلحات الإيموجي
أسئلة يطرحها المطورون عن الإيموجي
لماذا يكون طول الإيموجي 2 في JavaScript؟
سلاسل JavaScript مرمّزة بـ UTF-16، وأي حرف أعلى من U+FFFF يشغل وحدتين من 16 بت، أي زوجًا بديلًا. تقع معظم الإيموجي تقريبًا فوق U+FFFF، لذا حتى 😀 البسيط تكون قيمة .length له 2. استخدم [...str].length لعدّ نقاط الترميز، أو Intl.Segmenter لعدّ ما يراه القارئ.
ما أفضل تعبير نمطي لمطابقة الإيموجي؟
في JavaScript الحديثة، يطابق /\p{RGI_Emoji}/v تسلسلات الإيموجي الكاملة، بما فيها الأعلام وأزرار المفاتيح، لكن فقط حتى إصدار Unicode الذي يعرفه المحرّك. أما \p{Extended_Pictographic} فيُفلت الأعلام وأزرار المفاتيح، و\p{Emoji} يطابق خطأً الأرقام و# و*.
كيف أعدّ الإيموجي كحرف واحد؟
عُدّ عناقيد الوحدات الكتابية. في JavaScript استخدم Intl.Segmenter مع granularity grapheme؛ وفي Swift يفعل String.count ذلك أصلًا؛ وفي Python استخدم \X من وحدة regex الخارجية؛ وفي PHP استخدم grapheme_strlen من امتداد intl.
كم بايتًا أحتاج لتخزين إيموجي واحد؟
في UTF-8، بين 3 و35 بايت. الإيموجي ذو نقطة الترميز الواحدة فوق U+FFFF يشغل 4. درجات لون البشرة والأعلام والتسلسلات المتصلة تزيد الحجم: في اختبارنا اتسع 59% من جميع الإيموجي في 8 بايتات، وأطولها، وهو قبلة بدرجتين للون البشرة، يشغل 35.
لماذا يتحول الإيموجي إلى ???? في قاعدة البيانات؟
العمود أو الاتصال يستخدم مجموعة أحرف لا تستوعب الأحرف ذات الأربعة بايتات، وغالبًا ما تكون utf8 القديمة في MySQL. حوّل الجدول والاتصال معًا إلى utf8mb4. تشرح صفحة محوّل Unicode لدينا عبارة ALTER TABLE بالتفصيل.
