التخطّي إلى المحتوى
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
البدءWebReactSwiftUIJetpack ComposeFlutterReact Nativeالإعدادات الجاهزةنظام التصميم الخاص بكاستخدام Tanrei مع وكلاء الذكاء الاصطناعيالعلامة التجاريةرموز التصميمأنظمة التصميم والتخصيصTheme Studioاللونالطباعةالأيقوناتالشكلالارتفاعالحركةحالات التفاعلالسلوكالتخطيطإمكانية الوصولالكلماترموز الألوانرموز السلّم الطباعيرموز الشكلرموز الارتفاعرموز الحركةرموز الحالةرموز التباعد والقياسالمكوّناتالزرزر الإجراء الرئيسيالزر المقسَّممجموعة الأزرارزر مع قائمةشريط الأدواتزر النسخخانة الاختيارزر الاختيارمفتاح التبديلالرقاقةقائمة الخياراتشريط التمريرحقل شريط التمريرالتقويمDateTimePickerمجموعة الحقولمجموعة أزرار الاختيارمجموعة خانات الاختيارعنصر الاختيارحقل الاختيارشريط القوائمرفع الملفاتمنتقي الملفات متقدّمساحة الإفلاتحقل إدخال النص متقدّمحقل النصحقل الرقاقاتالحقلمربع التحرير والسرد متقدّمحقل التحرير والسردحقل إدخال العدد متقدّمحقل العددحقل إدخال اللون متقدّمحقل اللونحقل إدخال الرمز لمرة واحدة متقدّمحقل الرمز لمرة واحدةحقل إدخال النص المدمج متقدّممربع كتابة الرسائلالبطاقةالفاصلالطبقة النمطية متقدّممربع الحوارالقائمة (list)صف القائمةاللوحة السفليةاللوحة الجانبيةالعرض الدوّاركتلة الشيفرةالجدولاللوحة المنبثقةالخطواتالأكورديونقائمة الوصفالشكل التوضيحيهيكل التطبيقرأس الصفحةالعرض الشجريالخط الزمنيمعاينة الصورمقبض تغيير الحجمالإطار القابل لتغيير الحجممنطقة التمريرالشارةمؤشر التقدمالعدّادعارض الأرقام المتحرّكمؤشر التحميلشريط الإشعار (Snackbar)التلميحاختصار لوحة المفاتيحالوسمالشيفرة المضمَّنةالتنويهالصورة الرمزيةالحالة الفارغةمجموعة الوسومهيكل التحميلعنصر الملفمربع اللونالنص المتحركعلامات التبويبعرض التبويباتشريط التطبيقشريط التنقلدرج التنقلالبحثالرابطالتنقل بين الصفحاتروابط السابق والتاليمؤشر الخطواتمسار التنقلجدول المحتوياتبطاقة الرابطقائمة FABالحالةكيف يُبنى Tanreiالمساهمةالترخيص والإسناد
الرئيسيةالأسسالكلمات

الكلمات

ليس للمكوّن كلمات خاصة به. كل تسمية واسم ورسالة يرسمها المكوّن أو يقولها هي لمنتجك، تُعطى على المكوّن، أو مرة واحدة للمنتج كله، أو تؤخذ من حزمة الكلمات الإنجليزية التي تستوردها.

content_copyعرض بتنسيق Markdown

ليس للمكوّن كلمات خاصة به. ليس لأي مكوّن نص افتراضي بأي لغة: لا «مطلوب» ولا «إغلاق» ولا «موافق». الكلمة التي يرسمها المكوّن أو يقولها كلمة أعطاها منتجك: كتبتها أنت، أو أخذتها من حزمة الكلمات الإنجليزية التي تستوردها بنفسك.

القيمة الافتراضية تخفي الإخفاق الذي تسبّبه. المنتج العربي الذي ينسى كلمة واحدة يعرض الإنجليزية بجانب نصه، ولا شيء يبلّغ عن ذلك. والمنتج الإنجليزي له صوته هو أيضًا («Mandatory» و«Done» و«Nothing found»). ولذلك ليست عند المكوّن كلمة يرجع إليها، وهو يخبرك حين تنقصه كلمة. وحزمة الكلمات الإنجليزية ليست مرجعًا من هذا النوع: لا شيء يطبّقها إلا استيرادك أنت.

من أين تأتي الكلمة

ينظر المكوّن في ثلاثة مواضع، بهذا الترتيب، ولا ينظر في غيرها.

الموضع ما يعطيه مثال
المكوّن نفسه الكلمات التي تخص هذا الشيء وحده، وكل كلمة يقولها هذا المكوّن بطريقة مختلفة label، error-text؛ close-label="إغلاق عوامل التصفية"
كلمات منتجك الكلمات التي تقولها بالطريقة نفسها أينما رُسم الجزء، وتُعطى مرة واحدة لكل لغة، أو حزمة الكلمات الإنجليزية التي تستوردها requiredText، closeLabel، previousMonthLabel
المنصة ما عند المتصفح أصلًا بلغة القارئ أسماء الأشهر وأيام الأسبوع، والأرقام، و«ص»، ورسالة المتصفح نفسه عن قيمة ناقصة

وبعد هذه المواضع لا شيء: لا لغة يُرجع إليها، ولا إنجليزية «مؤقتًا».

حزمة الكلمات الإنجليزية

يكتب Tanrei لك مجموعة واحدة من كلمات منتجك: كلمة بإنجليزية بسيطة ومحايدة لكل مفتاح، مثل «Required» و«Close» و«Previous month». استوردها مرة واحدة:

import '@tanrei/web/words/en';

تعطي كلمات الصفحة كلها، كما يفعل استدعاء واحد للدالة setWords(). ولا شيء يستوردها نيابةً عنك: لا @tanrei/web ولا @tanrei/react ولا أي مكوّن ولا السمة lang للصفحة. وكلمة الحزمة لكل مفتاح موجودة في جدول المفاتيح.

  • كلماتك أنت تبقى الغالبة. استدعِ setWords() بعد الاستيراد، فتحلّ المفاتيح التي تعطيها محل كلمات الحزمة؛ والكلمة المعطاة على المكوّن تغلب الاثنتين. والاستيراد يعطي كل المفاتيح حين يُنفَّذ، فالكلمة التي أعطيتها قبله تُستبدل.
  • ابدأ من كلماتها. الوحدة تصدّر الكلمات نفسها قيمةً أيضًا: import { englishWords } from '@tanrei/web/words/en'.
  • في منتج بلغة أخرى، المفتاح الذي لا تحتويه مجموعة لغتك يُقرأ من مجموعة الصفحة، وهي الإنجليزية إن كانت الحزمة مستوردة. فلا تستوردها هناك إلا حيث تكون الإنجليزية هي الكلمة التي تريدها لمفتاح لم تكتبه؛ ومن دونها يُبلَّغ عن الكلمة الناقصة.
  • لا تخفي ما لا ينبغي إخفاؤه. مع استيراد الحزمة يكون لكل مفتاح كلمة، فتعيد missingWords() قائمة فارغة. ولتجد الكلمات التي لم يكتبها منتجك بنفسه بعد، اسألها في اختبار لا يستورد الحزمة.

وفي React يكون الاستيراد السطر نفسه، في الوحدة التي تُعرض أولًا.

أعطِ كلمات منتجك مرة واحدة

على الويب، استدعِ setWords() مرة واحدة، قبل تحميل المكوّنات أو بعده. والعناصر الموجودة في الصفحة أصلًا ترسم الكلمات فورًا.

import { setWords } from '@tanrei/web';

setWords({
  requiredText: 'مطلوب',
  optionalText: 'اختياري',
  closeLabel: 'إغلاق',
  clearLabel: 'مسح',
  targetLabel: 'يُفتح في تبويب جديد',
});

أعطِ كلمات المكوّنات التي تستخدمها فقط. والاستدعاء الثاني يضيف إلى الأول، مفتاحًا مفتاحًا؛ والمفتاح الذي يُعطى سلسلة نصية فارغة يُزال. والمسار @tanrei/web/words يصدّر الدالة نفسها من دون أن يعرّف أي عنصر، والحزمة @tanrei/react تصدّرها أيضًا.

وخاصية HTML على مكوّن واحد تغلب كلمات منتجك دائمًا:

الكلمة على المكوّن نفسه: حقل واحد يقولها بطريقة مختلفة

<tanrei-text-field label="البريد الإلكتروني" required required-text="مطلوب"></tanrei-text-field>
<tanrei-text-field label="الشركة" required required-text="لازم للفواتير"></tanrei-text-field>

أكثر من لغة

أعطِ مجموعة واحدة لكل لغة. يقرأ المكوّن مجموعة أقرب خاصية lang حوله، فالصفحة العربية، أو الجزء العربي من صفحة إنجليزية، يقرأ الكلمات العربية.

setWords({ requiredText: 'Required', closeLabel: 'Close' }, { lang: 'en' });
setWords({ requiredText: 'مطلوب', closeLabel: 'إغلاق' }, { lang: 'ar' });
  • المجموعة المعطاة للغة ar تجيب أيضًا عن ar-EG، إلا إذا أعطيت ar-EG مجموعة خاصة بها.
  • المفتاح الذي ليس في مجموعة اللغة يُقرأ من المجموعة المعطاة بلا lang.
  • الكلمات سلاسل نصية عادية. إن كانت في منتجك مكتبة للترجمة (localization)، فأعطِ السلاسل منها: الدالة setWords() ليست نظام ترجمة، وليست فيها قواعد للجمع. ولبعض الكلمات مواضع يملؤها المكوّن، ولا يُقرأ في الكلمة شيء غيرها.

كلمة فيها مواضع

في بعض الجمل قيمة لا يعرفها إلا المكوّن، لحظة يقولها: أين صار الصف بعد نقله، أو كم مُدخلة صنع اللصق. ومنتجك يظل يكتب الجملة كلها. فهو يعلّم مكان القيمة باسم بين قوسين معقوفين (braces)، والمكوّن يضع القيمة هناك.

setWords({ movedText: '{headline}: {position} of {count}' }, { lang: 'en' });
setWords({ movedText: 'تم نقل {headline} إلى الموضع {position} من {count}' }, { lang: 'ar' });

ترتيب الكلمات ترتيب لغتك. والمكوّن يملأ المواضع ولا يفعل بالجملة شيئًا آخر.

  • المكوّن هو الذي يقرّر الأسماء. لكل مفتاح مواضعه، وهي مذكورة في جدول المفاتيح، ولا يُملأ غيرها. ولك أن تكتب الموضع مرة واحدة، أو عدة مرات، أو ألا تكتبه.
  • العدد يُكتب بلغة العنصر. الموضعان {count} و {position} عددان صحيحان، يُكتبان بأرقام أقرب خاصية lang وبفواصل المجموعات فيها.
  • النص يحتفظ باتجاهه. الموضعان {headline} و {entry} نصان يحملهما المكوّن: العنوان الرئيسي لصف، ومُدخلة كتبها الشخص. يوضعان نصًّا عاديًّا، لا ترميزًا أبدًا. وحيث تُرسم الجملة أو تُعلَن يُعزل النص، فالاسم اللاتيني داخل جملة عربية يبقى على ترتيبه.
  • لكتابة قوس معقوف، اكتبه مرتين. الكتابة {{ تُرسم { والكتابة }} تُرسم }. والكلمة التي بلا مواضع لا تُقرأ إطلاقًا، ففيها يُكتب القوس مرة واحدة.
  • موضع واحد لا بد منه. المفتاح entryRemoveLabel يسمّي زر الإزالة لكل مُدخلة في حقل الرقاقات. ومن دون {entry} يكون لكل الأزرار الاسم نفسه.

أثناء التطوير تبلّغ وحدة التحكم عن موضع ليس للمفتاح ({entri})، وعن قوس مفرد، وعن غياب {entry}. والجملة تُستخدم مع ذلك كما كتبتها بالضبط. الدالة setWords() تبلّغ عنها حين تعطي الكلمة، والمكوّن يبلّغ عنها حين يستخدم كلمة معطاة على العنصر.

الجمع

ليست عند المكوّن قواعد نحوية: لا صيغ للجمع، ولا أعداد ترتيبية، ولا اختيار بين جملتين. ولك ثلاث طرائق للتعامل مع العدد، ولا تحتاج أي منها إلى شيء من Tanrei.

  1. اكتب جملة تصحّ مع كل عدد: {position} من {count}، عدد ما أُضيف: {count}.
  2. أعطِ الكلمة على المكوّن، وغيِّرها حين يتغيّر العدد. فمنتجك يعرف العدد، والكلمة التي على المكوّن تغلب دائمًا. وقواعد الجمع عندك هي التي تختار الجملة.
  3. حيث يحمل منتجك الأعداد بنفسه لا تكون في الكلمة مواضع: فحالة الجدول («1 إلى 20 من 132») سلسلة نصية واحدة كاملة تكتبها أنت.

من دون كلمة

الكلمة الناقصة عيب في المنتج، كحقل نص بلا تسمية. والمكوّن يجعل أثره صغيرًا على الشخص وظاهرًا لك.

  • الكلمات التي كانت ستُرسم لا تُرسم. حيث تقول علامة الشيء نفسه تقف العلامة في مكانها: نجمة لوسم المطلوب، وأيقونتا الإغلاق وعلامة الصح للإجراءين «إلغاء» و«موافق» في المنتقي، و +1 لعبارة «اليوم التالي». وحيث لا علامة تقوله لا يُرسم شيء ويحتفظ الموضع بحجمه.
  • عنصر التحكم الذي يحتاج إلى اسم يُرسم مع ذلك ويعمل. أزرار التنقل في التقويم، ومقبض تغيير الحجم في حقل النص، وإجراءات المنتقي تظل تعمل بلا اسم. والاستثناء الوحيد زرّا التدرّج في حقل العدد: الزر الذي بلا اسم يُخفى عن التقنيات المساعدة، لأن الزر الدوّار الذي بجانبه يغيّر العدد بنفسه.
  • يُبلَّغ عنها. أثناء التطوير تسمّي وحدة التحكم (console) خاصية HTML، مرة واحدة لكل كلمة وصفحة: <tanrei-field>: required-text is empty and the product's words have no requiredText.

عنصر التحكم الذي ليس له اسم متاح للتقنيات المساعدة يخذل من يستخدمون قارئ شاشة. والبلاغ موجود لتصلح الأمر، لا ليجوز لك تركه.

أفشِل الاختبار عند كلمة ناقصة

الدالة missingWords() تعيد المفاتيح التي احتيج إليها منذ تحميل الصفحة ولم يعطها أحد. اسألها في نهاية اختبار يمرّ على شاشات منتجك. والصفحة التي تستورد حزمة الكلمات الإنجليزية لا تنقصها كلمة.

import { missingWords } from '@tanrei/web';

test('كل كلمة يحتاج إليها المنتج معطاة', async () => {
  // …اعرض شاشات المنتج…
  expect(missingWords()).toEqual([]);
});

ما ليس كلمة

رسم الأيقونة والنجمة والشرطة والرقم والفاصل الذي تعطيه الإعدادات المحلية ليست كلمات، والمكوّن يظل يرسمها. وكذلك الأسماء التي في واجهة البرمجة (خاصية أو قيمة)، وما تقوله وحدة التحكم للمطوِّر، وهو بالإنجليزية. والمكتوب الذي يرسمه اختصار لوحة المفاتيح («Ctrl» و«Enter» و ⌘) هو ما تحمله مفاتيح لوحة المفاتيح نفسها؛ أما ما يُقرأ بصوت مسموع للاختصار فهو القيمة label التي تعطيها إياه.

المفاتيح

كل مفتاح، والخصائص التي تقرؤه، والمواضع التي يملؤها المكوّن في جملته، وكلمته في حزمة الكلمات الإنجليزية. معظم المفاتيح لها اسم خاصيتها. وثلاثة تختلف: الخاصيتان label و failedLabel في زر النسخ تقرآن copyLabel و copyFailedLabel، والخاصية overflowLabel في مسار التنقل تقرأ breadcrumbsOverflowLabel.

المفتاح تقرؤه المواضع التي تُملأ في حزمة الكلمات الإنجليزية
addedLabel code-block.addedLabel Added
attachLabel chat-composer.attachLabel Attach files
avatarGroupOverflowLabel avatar.overflowLabel {count} {count} more
backLabel search.backLabel Back
breadcrumbsOverflowLabel breadcrumbs.overflowLabel Show hidden levels
busyLabel button.busyLabel Loading
cancelLabel date-time-picker.cancelLabel Cancel
cancelledText file-upload.cancelledText, list.cancelledText {label}, {position}, {count} {label} returned to {position} of {count}
characterCountNoLimitText text-input.counterNoLimitText {count} Characters: {count}
characterCountText text-input.counterText {count}, {limit}, {remaining} Characters: {count} of {limit}
clearLabel combobox.clearLabel, navigation-drawer.filterClearLabel, search.clearLabel, tree-view.filterClearLabel Clear
closeLabel bottom-sheet.closeLabel, callout.closeLabel, image-preview.closeLabel, popover.closeLabel, side-sheet.closeLabel, snackbar.closeLabel Close
confirmLabel date-time-picker.confirmLabel OK
copiedLabel code-block.copiedLabel, copy-button.copiedLabel Copied
copyFailedLabel code-block.copyFailedLabel, copy-button.failedLabel Could not copy
copyLabel code-block.copyLabel, copy-button.label Copy
dateLabel date-time-picker.dateLabel Date
decrementLabel number-input.decrementLabel Decrease
dialLabel date-time-picker.dialLabel Dial
dragHandleHint file-upload.dragHandleHint, list.dragHandleHint Press Space to lift, the arrow keys to move, Space to drop, Escape to cancel; Enter opens the moves
dragHandleLabel file-upload.dragHandleLabel, list.dragHandleLabel {label} Reorder {label}
dropZoneRefusingText drop-zone.refusingText These files cannot be added here
dropZoneText drop-zone.dropText Drop files here
duplicateEntryText chip-field.duplicateText This is already in the list.
editLabel tree-view.editLabel Name
emptyText combobox.emptyText No matches
endLabel calendar.endLabel, date-time-picker.endLabel End
entriesAddedText chip-field.addedCountText {count} Added: {count}
entryAddedText chip-field.addedText {entry} {entry} added
entryLimitText chip-field.limitText No more can be added.
entryModeLabel date-time-picker.entryModeLabel Input mode
entryRemoveLabel chip-field.removeLabel {entry} Remove {entry}
entryRemovedText chip-field.removedText {entry} {entry} removed
expandLabel snackbar.expandLabel Show full message
externalLabel link.externalLabel, link-card.externalLabel external link
fileCountErrorText chat-composer.countErrorText, file-upload.countErrorText No more files can be added.
fileSizeErrorText chat-composer.sizeErrorText, file-upload.sizeErrorText This file is too large.
fileTypeErrorText chat-composer.typeErrorText, file-upload.typeErrorText This kind of file cannot be added.
filesDroppedText drop-zone.droppedText {count} Files added: {count}
filesRefusedText drop-zone.refusedText {count} Files not added: {count}
filterCountText navigation-drawer.filterCountText, tree-view.filterCountText {count} Matches: {count}
filterEmptyText navigation-drawer.filterEmptyText, tree-view.filterEmptyText No matches
filterPlaceholder navigation-drawer.filterPlaceholder, tree-view.filterPlaceholder Filter
firstPageLabel pagination.firstLabel First page
handleLabel bottom-sheet.handleLabel Resize sheet
highlightedLabel code-block.highlightedLabel Highlighted
hourLabel date-time-picker.hourLabel Hour
imageCountText image-preview.countText {position}, {count} {position} of {count}
imageErrorText image-preview.errorText This image could not be loaded
incrementLabel number-input.incrementLabel Increase
lastPageLabel pagination.lastLabel Last page
liftedText file-upload.liftedText, list.liftedText {label}, {position}, {count} {label} lifted: {position} of {count}
loadingLabel image-preview.loadingLabel, navigation-drawer.loadingLabel, skeleton.label, tree-view.loadingLabel Loading
marqueePauseLabel marquee-text.pauseLabel Pause scrolling text
marqueePlayLabel marquee-text.playLabel Play scrolling text
menubarOverflowLabel menubar.overflowLabel More
minuteLabel date-time-picker.minuteLabel Minute
moveAfterLabel file-upload.moveAfterLabel, list.moveAfterLabel Move down
moveBeforeLabel file-upload.moveBeforeLabel, list.moveBeforeLabel Move up
moveToEndLabel file-upload.moveToEndLabel, list.moveToEndLabel Move to bottom
moveToStartLabel file-upload.moveToStartLabel, list.moveToStartLabel Move to top
movedText file-upload.movedText, list.movedText {label}, {position}, {count} {label}: {position} of {count}
nextDayLabel date-time-picker.nextDayLabel next day
nextImageLabel image-preview.nextLabel Next image
nextItemLabel carousel.nextLabel Next slide
nextMonthLabel calendar.nextMonthLabel, date-time-picker.nextMonthLabel Next month
nextPageLabel pagination.nextLabel Next page
noFileText file-upload.emptyText No file chosen
noOptionsText select.emptyText No options
optionalText field.optionalText, field-group.optionalText Optional
pageCountText pagination.countText {page}, {count} Page {page} of {count}
pageLabel pagination.pageLabel {page} Page {page}
pauseLabel carousel.pauseLabel Pause slideshow
periodLabel date-time-picker.periodLabel AM/PM
playLabel carousel.playLabel Play slideshow
previousImageLabel image-preview.previousLabel Previous image
previousItemLabel carousel.previousLabel Previous slide
previousMonthLabel calendar.previousMonthLabel, date-time-picker.previousMonthLabel Previous month
previousPageLabel pagination.previousLabel Previous page
rangeErrorText date-time-picker.rangeErrorText End must be after start
rangeLabel date-time-picker.rangeLabel Range
removeLabel file-item.removeLabel {name} Remove {name}
removedLabel code-block.removedLabel Removed
requiredText field.requiredText, field-group.requiredText Required
resizeLabel text-input.resizeLabel Resize
retryLabel file-item.retryLabel {name} Retry {name}
segmentEmptyText text-input.segmentEmptyText Empty
selectAllLabel table.selectAllLabel Select all rows
sendLabel chat-composer.sendLabel Send
sortedAscendingLabel table.sortedAscendingLabel sorted ascending
sortedDescendingLabel table.sortedDescendingLabel sorted descending
startLabel calendar.startLabel, date-time-picker.startLabel Start
stepCompletedText stepper.completedText Completed
stepErrorText stepper.errorText Has an error
stepPositionText stepper.positionText {position}, {count} Step {position} of {count}
stopLabel chat-composer.stopLabel Stop
targetLabel button.targetLabel, card.targetLabel, chip.targetLabel, link.targetLabel, link-card.targetLabel, list/list-item.targetLabel, menu/item.targetLabel, menubar/item.targetLabel, navigation-bar/destination.targetLabel, navigation-drawer/destination.targetLabel, search/result.targetLabel, search/result-group.targetLabel opens in a new tab
todayLabel calendar.todayLabel, date-time-picker.todayLabel Today
toolbarOverflowLabel toolbar.overflowLabel More actions
wheelsLabel date-time-picker.wheelsLabel Wheels
yearViewLabel calendar.yearViewLabel, date-time-picker.yearViewLabel Choose a year
zoomInLabel image-preview.zoomInLabel Zoom in
zoomOutLabel image-preview.zoomOutLabel Zoom out

الكلمات التي تخص شيئًا واحدًا ليس لها مفتاح، كالقيمة label لحقل، والقيمة label لمنتقي التاريخ والوقت، وعبارة «إزالة Ada Lovelace» على زر إزالة، ونص حالة فيه عدد: أعطِها على المكوّن.

المنصات الأخرى

القاعدة واحدة على كل المنصات. والويب وحده مبنيّ حتى الآن؛ أما SwiftUI و Compose و Flutter و React Native فستأخذ كلمات المنتج، وحزمة الكلمات الإنجليزية نفسها، بأسلوب كل منصة.