الواجهة البرمجية للتدويل في ريلز
توفّر جوهرة روبي I18n (اختزالٌ للمصطلح "التدويل" [internationalization]) التي تُشحن مع ريلز (بدءًا من الإصدار 2.2) إطارًا سهل الاستخدام وقابلًا للتوسعة لترجمة تطبيقك إلى لغة مخصصّة واحدة بخلاف الإنجليزية أو لتوفير دعم بلغات متعدّدة في تطبيقك.
عادةً ما تعني عمليّة "التدويل" تجريد كل السلاسل النصية والبتات الخاصة الأخرى ذات العلاقة باللغة (مثل تنسيقات التاريخ أو العملة) من تطبيقك. تعني عملية "التوطين" توفير الترجمات والصيغ المحلية لهذه البتات.
لذلك، يجب عليك في عمليّة تدويل تطبيقك ريلز:
- التأكّد من دعمك للواجهة البرمجية i18n.
- إخبار ريلز بمكان العثور على قواميس اللغة.
- إخبار ريلز بكيفيّة إعداد اللغة المحليّة والمحافظة عليها وتبديلها.
في عملية توطين تطبيقك، سترغب غالبًا بالقيام بالأشياء الثلاثة التالية:
- استبدال أو استكمال الإعدادات الافتراضية لريلز؛ على سبيل المثال، تنسيقات التاريخ والوقت، وأسماء الشهور، وأسماء نماذج السجلات الفعالة (Active Record)، ...إلخ.
- تجريد السلاسل النصيّة في تطبيقك إلى قواميس مفتاحي؛ مثلًا الرسائل الوامضة (flash messages)، والنص الثابت في عروضك، ...إلخ.
- تخزين القواميس الناتجة في مكان ما.
سيرشدك هذا الدليل إلى استعمال الواجهة البرمجية I18n كما سيعلمك كيفيّة تدويل تطبيق ريلز من البداية.
ستتعلم بعد قراءة هذا الدليل:
- كيف تعمل الواجهة البرمجية I18n في ريلز.
- كيفيّة استخدام I18n بشكل صحيح في تطبيق RESTful بطرق مختلفة.
- كيفيّة استخدام I18n لترجمة أخطاء السجل الفعال أو مواضيع البريد الإلكتروني للإجراء
Mailer
- بعض الأدوات الأخرى لمواصلة ترجمة تطبيقك.
ملاحظة: يوفّر لك إطار روبي I18n جميع الوسائل اللازمة لتدويل / توطين تطبيقك ريلز. يمكنك أيضًا استخدام مختلف الجواهر المتاحة لإضافة وظائف أو ميزات إضافية. انظر جوهرة ريلز i18n لمزيد من المعلومات.
كيفيّة عمل الواجهة البرمجية I18n في ريلز
التدويل مشكلة معقدة. تختلف اللغات الطبيعية بطرق كثيرة (مثلًا في قواعد الجمع) بحيث يصعب توفير أدوات لحل جميع المشاكل في وقت واحد. لهذا السبب، تركّز الواجهة I18n البرمجية في ريلز على:
- تقديم الدعم للغة الإنجليزية واللغات المماثلة مباشرة.
- تسهيل تخصيص كل شيء للغات الأخرى وتوسيعها.
دُوّلت (internationalized) كل سلسلة نصية ثابتة في إطار ريلز كجزء من هذا الحل - مثل رسائل التحقق من السجلّات الفعالة، وتنسيقات الوقت والتاريخ. يعني توطين تطبيق ريلز تعريف القيم المترجمة لهذه السلاسل في اللغات المطلوبة.
لتوطين وتخزين وتحديث المحتوى في تطبيقك (ترجمة منشورات مدوّنة مثلًا)، راجع قسم ترجمة محتوى النماذج.
المعمارية العامة للمكتبة
تُقسّم جوهرة روبي I18n إلى قسمين:
- واجهة برمجة التطبيقات العامة لإطار عمل i18n - وحدة روبي مع التوابع العامّة التي تحدد كيّفية عمل المكتبة.
- خلفيّة افتراضيّة (والتي تسمى عمدًا خلفية "بسيطة") تُنفّذ هذه التوابع
بصفتك مستخدمًا، عليك دائمًا الوصول إلى التوابع العامّة عبر الوحدة I18n فقط، ولكن من المفيد معرفة قدرات الخلفّية.
من الممكن تبديل الواجهة الخلفية البسيطة التي شُحنت بأخرى أكثر فعاليّة، والتي ستخزن بيانات الترجمة في قاعدة بيانات علائقيّة، أو قاموس GetText أو ما شابه ذلك. انظر القسم استخدام الخلفيات المختلفة أدناه.
الواجهة I18n البرمجية العامة
أهم توابع الواجهة I18n البرمجية هي:
translate # تبحث عن ترجمة النص
localize # توطّن كائنات التاريخ والوقت لتنسيقات محليّة
يملك كل تابع من هذين التابعين اسمًا بديًلًا هو: t.
و l.
فتستطيع استخدامها بالشكل التالي:
I18n.t 'store.title'
I18n.l Time.now
توجد أيضًا قارئات وكاتبات خاصيات للخاصيات التالية:
load_path # لإعلان عن ترجمة ملفاتك المحلية
locale # جلب وضبط اللغة المحلية
default_locale # جلب وضبط اللغة المحلية الإفتراضيّة
available_locales # وضع اللغات المحليّة المتوفّرة لتطبيقك في قائمة بيضاء
enforce_available_locales # (true او false) أجبر وضع اللغات المحليّة في القائمة البيضاء
exception_handler # مختلفًا exception_handler استخدم
backend # استخدم خلفية مختلفة
لذا، فلنُدّول تطبيق ريلز بسيط من الألف إلى الياء في الفصول القادمة!
إعداد تطبيق ريلز للتدويل
توجد بعض الخطوات لتشغيل دعم I18n لتطبيق ريلز.
إعداد وحدة I18n
وفقّا لفلسفة ريلز الناصّة على أن "العرف فوق الضبط" (convention over configuration) يوفّر ريلز I18n سلاسل ترجمة نصيّة معقولة. يمكن إعادة تعريفها عندما تحتاج إلى سلاسل ترجمة مختلفة.
ويضيف ريلز كل من الملفّات بصيغة rb. و yml. من المجلّد config/locales إلى "مسار تحميل الترجمة" (translations load path) تلقائيًّا.
تحتوي اللغة المحليّة (locale) في هذا المجلّد على زوج من سلاسل الترجمة:
en:
hello: "Hello world"
يعني هذا أنه في اللّغة en:
، سيعيَّن المفتاح "hello" إلى السلسلة النصيّة "Hello world". تُدوّل كل سلسلة داخل ريلز بهذه الطريقة؛ انظر على سبيل المثال لرسائل التحقق من صحّة النموذج الفعال في الملف activemodel/lib/active_model/locale/en.yml أو تنسيقات الوقت والتاريخ في الملف activesupport/lib/active_support/locale/en.yml. يمكنك استخدام YAML أو جداول Hash القياسيّة لتخزين الترجمات في الخلفيّة (البسيطة) الافتراضية.
ستستخدم مكتبة I18n اللغة الإنجليزية كلغة افتراضية أي إن لم تُعيّن لغة مختلفة سيُستخدم en:
للبحث عن الترجمات.
ملاحظة: تستعمل المكتبة i18n "مقاربة واقعية" (pragmatic approach) لمفاتيح الإعدادات المحلية (بعد بعض النقاش) بأخذها جزء اللّغة فقط بالحسبان مثل en:
و pl:
لا جزء المنطقة معها مثل en-US:
أو en-GB:
التي تُستخدَم عادةً لفصل "اللغات" و "الإعدادات الإقليميّة" أو "اللهجات". تستخدم عدّة تطبيقات دوليّة عنصر اللغة فقط مثل: cs:
أو th:
أو es:
(للتشيكيّة، والتايلاندية والإسبانية على التوالي). ومع ذلك تظل هناك أيضًا اختلافات إقليميّة قد تكون مهمّة داخل مجموعات لغويّة مختلفة. على سبيل المثال في الإعداد المحلّي en-US:
رمز العملة هو $
بينما هو £
في en-GB:
. لا شيء يمنعك من فصل الإعدادات الإقليمية والأخرى بهذه الطريقة: عليك فقط توفير إعداد اللغة المحليّة "الإنجليزية - المملكة المتحدة" الكاملة في قاموس en-GB:
.
مسار تحميل الترجمات (I18n.load_path) هو مصفوفة مسارات إلى الملفّات التي ستُحمّل تلقائيًا. يتيح إعداد هذا المسار تخصيص بنية مُجلّد الترجمة ومخطّط تسمية الملفّات.
ملاحظة: تحمّل الواجهة الخلفيّة هذه الترجمات بكَسَل (lazy-load) عندما يُبحث في الترجمة للمرّة الأولى. يمكن تبديل هذه الواجهة الخلفيّة بشيء آخر حتى بعد الإعلان عن الترجمة بالفعل.
يمكنك تغيير اللغة الإفتراضيّة بالإضافة لإعداد مسارات تحميل الترجمات في الملف config/application.rb كما يلي:
config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :de
يجب تحديد مسار التحميل قبل البحث في أي ترجمات. لتغيير اللغة الافتراضيّة من مُهيّئ (initializer) بدلًا من الملف config/application.rb:
# config/initializers/locale.rb
# عن ملفّات الترجمة I18n المكان حيث يجب أن تبحث المكتبة
I18n.load_path += Dir[ريلز.root.join('lib', 'locale', '*.{rb,yml}')]
# ضع الإعدادات المحليّة المتوفّرة بقائمة بيضاء
I18n.available_locales = [:en, :pt]
# :en ضبط الإعداد المحلّي الإفتراضي كشيء مختلف عن
I18n.default_locale = :pt
إدارة الإعدادات المحليّة عبر الطلبات
تُستخدم الإعدادات المحليّة الإفتراضيّة لكافة الترجمات ما لم يُضبط I18n.locale بشكل صريح.
من المحتمل أن يحتاج التطبيق المترجم إلى توفير الدعم للعديد من اللغات. ولتحقيق ذلك يجب تعيين الإعدادات المحليّة في بداية كل طلب بحيث تُترجم جميع السلاسل باستخدام اللغة المطلوبة خلال حياة هذا الطلب.
يمكن تعيين اللغة في before_action في ApplicationController:
before_action :set_locale
def set_locale I18n.locale = params[:locale] || I18n.default_locale end |
يوضّح هذا المثال ذلك باستخدام معاملة استعلام URL لتعيين الإعداد المعلّي (على سبيل المثال http://example.com/books?locale=pt ). باستخدام هذه الطريقة، يُصيّر http://localhost:3000?locale=pt التوطين البرتغالي بينما يحمّل http://localhost:3000?locale=de loads توطينًا باللغة الألمانية.
يمكن تعيين اللغة باستخدام أحد طرق مختلفة كثيرة.
ضبط الإعدادات المحلية من اسم النطاق (Domain Name)
أحد الخيارات لديك هو تعيين اللغة من اسم النطاق حيث يشتغل تطبيقك. نريد مثلًا أن يُحمّل www.example.com اللغة الإنجليزية (أو اللغة الإفتراضيّة)، و www.example.es اللغة الإسبانيّة. بحيث يُستخدم اسم النطاق ذو المستوى الأعلى لإعداد الإعدادات المحليّة. ولهذا الأسلوب العديد من المزايا:
- يصبح الإعداد المحلّي جزءًا واضحًا من عنوان URL.
- يفهم الناس تلقائيًّا بأي لغة سيُعرض المحتوى.
- لتنفيذ هذا في ريلز في قمّة البساطة.
- يبدو أن محركات البحث تحبّ وجود المحتوى بلغات مختلفة في نطاقات مختلفة لكن مترابطة.
يمكنك تعريف إستخدامه على هذا النحو في وحدة تحكّمك ApplicationController:
before_action :set_locale
def set_locale I18n.locale = extract_locale_from_tld || I18n.default_locale end # إن لم يوجد مثل هذا الإعداد +nil+ تحصّل على إعدادات محليّة من أعلى مستوى النطاق أو ردّ # عليك وضع شيء مثل : # 127.0.0.1 application.com # 127.0.0.1 application.it # 127.0.0.1 application.pl # إن أردت تجربة هذا محليًّا /etc/hosts بملفّك def extract_locale_from_tld parsed_locale = request.host.split('.').last I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end |
يمكننا أيضًا تعيين اللغة أو الإعداد المحلّي من النطاق الفرعي بطريقة مشابهة جدًا:
# ( http://it.application.local:3000 )تحصّل على كود الإعداد المحلّي من نطاق الطلب الفرعي مثل
# عليك وضع شيء مثل : # 127.0.0.1 gr.application.local # إن أردت تجربة هذا محليًّا /etc/hosts بملفّك def extract_locale_from_subdomain parsed_locale = request.subdomains.first I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end |
إذا تضمّن تطبيقك قائمة تبديل لغة، ستجد فيها شيئًا من هذا القبيل:
link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['PATH_INFO']}") |
على افتراض أنك ستُعيّن APP_CONFIG[:deutsch_website_url] على قيمة ما مثل http://www.application.de.
لهذا الحل مزايا سالفة الذكر ومع ذلك قد لا تقدر أو لا ترغب في توفير توطينات مختلفة ( بمعنى "نُسخ من اللغات") في نطاقات مختلفة. سيكون الحل البديهي تضمين رمز الإعداد المحلّي في params URL (أو مسار الطلب).
ضبط الإعدادات المحليّة من Params URL
الطريقة الأكثر شيوعًا لتعيين (وتمرير) الإعداد المحلّي هو تضمينه في معاملات URL (أي params)، كما فعلنا في التابع before_action من I18n.locale = params[:locale] في المثال الأول. نرغبفي هذه الحالة في الحصول على عناوين URL مثل www.example.com/books?locale=ja أو www.example.com/ja/books.
يحتوي هذا التابع تقريبًا على نفس مجموعة المزايا من إعداد اللغة المحليّة من اسم النطاق: أي أنه RESTful و يتوافق مع باقي شبكة الويب العالمية. لكنّه يتطلّب المزيد من العمل لتعريف إستخدامه.
الحصول على الإعداد المحلّي من params وضبطها وفقًا لذلك ليس صعبًا؛ الصعب هو وضعه بكل عنوان URL وبالتالي تمريره عبر الطلبات. من الصعب والممل تضمين خيار صريح بكل عنوان URL مثلًا link_to(books_url(locale: I18n.locale)) إن لم يكن مستحيلًا بالطبع.
يحتوي ريلز على بنية أساسيّة لـ "مركزة القرارات الديناميكية حول عناوين URL" في ApplicationController#default_url_options، وهو مفيد تحديدًا في هذا السيناريو: فهو يمكّننا من تعيين "افتراضيّات" url_for و التوابع المساعدة المعتمدات عليه (عن طريق تعريف إستخدام/ إعادة تعريف default_url_options).
يمكننا تضمين شيء من هذا القبيل في ApplicationController لدينا ثم:
# app/controllers/application_controller.rb
def default_url_options { locale: I18n.locale } end |
كل تابع مساعد يعتمد على url_for (مثل مساعدي المسارات المسمّاة مثل root_path أو root_url، ومسارات الموارد مثل books_path أو books_url إلخ.) سيشمل الآن تلقائيًا الإعدادات المحليّة في سلسلة الاستعلام، بهذا الشكل:
http://localhost:3001/?locale=ja |
قد تكون راضيًا عن هذا. إلا أنّه يؤثّر على سهولة قراءة عناوين URL عند "تعليق" الإعدادات المحليّة في نهاية كل عنوان URL في تطبيقك. علاوة على ذلك من الناحيّة العندسيّة، عادةً ما تكون اللغة أو الإعداد المحلّي أعلى هرميًّا من الأجزاء الأخرى بنطاق التطبيق: ويجب أن تعكس عناوين URL ذلك.
سترغب غالبًا أن تبدو عناوين URL بالشكل: http://www.example.com/en/books (الذي يُحمّل اللغة الإنجليزية) و http://www.example.com/nl/books (الذي يُحمّل اللغة الهولندية). يمكن تحقيق ذلك من خلال استراتيجية " إعادة تعريف default_url_options " أعلاه: عليك فقط إعداد مساراتك باستخدام scope:
# config/routes.rb
scope "/:locale" do resources :books end |
الآن عند استدعاءك التابع books_path يجب أن تحصل على "/en/books" (للغة الإفتراضيّة). يجب أن يُحمّل عنوان URL مثل http://localhost:3001/nl/books باللغة الهولندية، وبعد ذلك يجب أن ترد النداءات إلى books_path السلسلة "/nl/books" (لأن اللغة تغيّرت).
نظرًا لأن قيمة الرد default_url_options تُخّزن مؤقتًا لكل طلب لا يمكن توليد عناوين URL في مُحدّد الإعدادات المحليّة (locale selector) تستدعي مساعدي بشكل حلقي (loop) يُعيّن I18n.locale المناسب في كل تكرار (iteration). اترك بدلاً من ذلك I18n.locale دون تغيير، مرّر خيارًا صريحًا locale: إلى المساعد، أو عدّل request.original_fullpath.
إن لم ترغب في فرض استخدام إعداد محلّي في مساراتك تستطيع استخدام نطاق مسار اختياري (يُشار إليه بالأقواس) على النحو التالي:
# config/routes.rb
scope "(:locale)", locale: /en|nl/ do resources :books end |
باستخدام هذه الطريقة لن تحصل على Routing Error عند الوصول إلى مواردك مثل http://localhost:3001/books بدون إعداد محلّي. هذا مفيد عندما تريد استخدام اللغة الإفتراضيّة عند عدم تحديدها.
يجب أن تمنح عناية خاصة لجذر العنوان URL (عادةً ما تكون "الصفحة الرئيسية" أو "لوحة المعلومات") بالتطبيق. لن يعمل عنوان URL مثل http://localhost:3001/nl تلقائيًا، نظرًا لأن التصريح books#index" :root to" في routes.rb لا يأخذ اللغة في الاعتبار. (ومعه الحق: هناك عنوان URL "جذر" واحد فقط.)
ستحتاج غالبًا لتعيين عناوين URL مثل هذه:
# config/routes.rb
get '/:locale' => 'dashboard#index' |
لا تهتم بشكل خاص بترتيب مساراتك لأن التصريح بهذا الشكل لا يعني "أكل" الأخريات. (قد ترغب في إضافته مباشرة قبل التصريح root :to.)
ألقي نظرة على مختلف الجواهر التي تبسّط العمل مع المسارات: routing_filter و rails-translate-routes وroute_translator
ضبط الإعدادات المحليّة من تفضيلات المستخدم
يستطيع تطبيق بمستخدمين مُستَوثَقين السماح لهم بضبط تفضيلات لغة من خلال واجهة التطبيق. بهذه الطريقة يظل تفضيله المحدّد لللغة في قاعدة البيانات ويُستخدم لتحديد الإعدادات المحليّة للطلبات المصادق عليها من طرف ذلك المستخدم.
def set_locale
I18n.locale = current_user.try(:locale) || I18n.default_locale end |
اختيار إعداد محلّي ملمّح (Choosing an Implied Locale)
عندما لا يُعيّن إعداد محلّي بصراحة لطلب (عبر إحدى التوابع المذكورة أعلاه مثلًا)، يجب أن يحاول التطبيق استنتاجه الإعداد المرغوب بنفسه.
استنباط الإعداد المحلّي من ترويسة اللغة
تشير الترويسة Accept-Language HTTP إلى اللغة المفضّلة لاستجابة الطلب. تُعيّن المتصفّحات قيمة هذه الترويسة استنادًا لإعدادات تفضيل اللغة للمستخدم ّمما يجعله اختيارًا أوّلًا جيدًا مرة عند استنباط لغة.
ما يلي مثال عن تعريف استخدام بسيط لترويسة Accept-Language:
def set_locale
logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" I18n.locale = extract_locale_from_accept_language_header logger.debug "* Locale set to '#{I18n.locale}'" end private def extract_locale_from_accept_language_header request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first end |
من الضروري من الناحية العمليّة وجود تعليمات برمجيّة أكثر قوة لفعل هذا بذلك بشكل يعتمد عليه. مكتبة إيان هيكر http_accept_language أو برمجيّة رايان تومايكو الوسيطة للإعدادات المحليّة مع Rack تقدّم حلولًا لهذه المشكلة.
استنباط الإعداد المحلّي من الموقع الجغرافي للعنوان IP
يمكن استخدام عنوان IP العميل الذي يقدم الطلب لاستنتاج منطقته وبالتالي إعداداته المحليّة. يمكن استخدام خدمات مثل GeoIP Lite Country أو gems مثل geocoder لتطبيق هذه الطريقة.
هذه الطريقة بشكل عام أقل موثوقية بكثير من استخدام ترويسة اللغة ولا يوصى بها لمعظم تطبيقات الويب.
تخزين الإعدادات المحليّة من الجلسة أو من ملفّات تعريف الارتباط (Cookies)
قد تميل إلى تخزين الإعدادات المحليّة المختارة في جلسة عمل أو ملف تعريف ارتباط (Cookie). لكن لا تفعل هذا. يجب أن يكون الإعداد المحلّي شفّافًا وجزءًا من عنوان URL. هكذا لن تخالف افتراضات الناس الأساسيّة حول الويب نفسه: إن أرسلت عنوان URL إلى صديق يجب أن يشاهد نفس الصفحة والمحتوى الذي تراه. المصطلح لهذا هو أنك RESTful. اقرأ المزيد عن مقاربة RESTful في مقالات Stefan Tilkov. في بعض الأحيان هناك استثناءات لهذه القاعدة وتلك التي نناقشها أدناه.
التدويل والتوطين
حسنا! الآن هيّئت الدعم I18n لتطبيقك Ruby on ريلز وأعلمته عن الإعداد المحلّي المستخدم وكيفيّة الحفاظ عليه بين الطلبات.
نحتاج بعد ذلك لتدويل طلبنا عن طريق تجريد كل عنصر خاص بالإعدادات المحليّة. وأخيرا، نحتاج لتوطينهم بتوفير الترجمات اللازمة لهذه المُجرّدات.
في المثال التالي:
# config/routes.rb
ريلز.application.routes.draw do root to: "home#index" end # app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :set_locale def set_locale I18n.locale = params[:locale] || I18n.default_locale end end # app/controllers/home_controller.rb class HomeController < ApplicationController def index flash[:notice] = "Hello Flash" end end # app/views/home/index.html.erb <h1>Hello World</h1> <p><%= flash[:notice] %></p> |
تجريد الشيفرات البرمجيّة المُترجمة (Abstracting Localized Code)
توجد سلسلتان باللغة الإنجليزية في الكود لدينا سيُصيّرها المستخدمون في ردّنا ("Hello Flash" و "Hello World"). لتدويل هذه الشيفرة يجب استبدال هذه السلاسل نداءات إلى المساعد t# مع مفتاح مناسب لكل سلسلة:
# app/controllers/home_controller.rb
class HomeController < ApplicationController def index flash[:notice] = t(:hello_flash) end end # app/views/home/index.html.erb <h1><%= t :hello_world %></h1> <p><%= flash[:notice] %></p> |
والآن عندما يُصيّر هذا العرض سيُظهر لك رسالة خطأ تخبرك بأن ترجمات للمفاتيح :hello_world و :hello_flash مفقودة.
يضيف ريلز تابعًا t ( ترجمة translate) مساعدًا لعروضك بحيث لا تحتاج إلى كتابة I18n.t كاملة طوال الوقت. بالإضافة إلى ذلك يمسك هذا المساعد الترجمات المفقودة ويلف رسالة الخطأ الناتجة في <span class="translation_missing">
توفير ترجمات للسلسلات المدوّلة
أضف الترجمات المفقودة إلى ملفات قاموس الترجمة:
# config/locales/en.yml
en: hello_world: Hello world! hello_flash: Hello flash! # config/locales/pirate.yml pirate: hello_world: Ahoy World hello_flash: Ahoy Flash |
تستخدم الترجمات الإعداد المحلّي en: نظرًا لعدم تغيير default_locale، وتصيّر الاستجابة السلاسل الإنجليزية:
إن عيّنت الإعدادات المحليّة عبر عنوان URL كإعدادات القرصنة ( http://localhost:3000?locale=pirate ) ستظهر الاستجابة سلاسل "القراصنة":
عليك إعادة تشغيل الخادم عند إضافة ملفّات إعدادات محليّة جديدة.
يمكنك استخدام ملفات YAML (إمتدادها yml.) أو Ruby عادي (امتدادها rb.) لتخزين ترجماتك في SimpleStore. لكن YAML هو الخيار المفضّل بين مطورّي ريلز. ومع ذلك لديه عيب واحد كبير. YAML حساس جدًا للمسافات البيضاء والأحرف الخاصّة، لذلك قد يُحمّل التطبيق القاموس بشكل صحيح. ستعطّل ملفّات Ruby تطبيقك من الطلب الأول بحيث يمكنك العثور على الخطأ بسهولة. (إذا واجهت أي "مشكلات غريبة" مع قواميس YAML حاول وضع الجزء ذي الصلة منها في ملف Ruby.)
إن خُزّنت ترجماتك في ملفات YAML يجب تهريب (escape) بعض المفاتيح. وهم:
- true, on, yes
- false, off, no
أمثلة:
# config/locales/en.yml
en: success: 'true': 'True!' 'on': 'On!' 'false': 'False!' failure: true: 'True!' off: 'Off!' false: 'False!' I18n.t 'success.true' # => 'True!' I18n.t 'success.on' # => 'On!' I18n.t 'success.false' # => 'False!' I18n.t 'failure.false' # => Translation Missing I18n.t 'failure.off' # => Translation Missing I18n.t 'failure.true' # => Translation Missing |
تمرير المتغيّرات للترجمات
أحد الاعتبارات الرئيسية للنجاح في تدويل طلب ما هو تجنّب عمل افتراضات خاطئة حول القواعد النحوية عند تجريد الشيفرة المُوطّنة. القواعد النحوية التي تبدو أساسية في لغة ما قد لا تكون صحيحة في لغة أخرى.
يظهر التجريد غير السليم في المثال التالي، حيث وُضعَت افتراضات حول ترتيب أجزاء الترجمة المختلفة. لاحظ أن ريلز يوفّر مساعد number_to_currency لمعالجة الحالة التالية.
# app/views/products/show.html.erb
<%= "#{t('currency')}#{@product.price}" %> # config/locales/en.yml en: currency: "$" # config/locales/es.yml es: currency: "€" |
إذا كان سعر المنتج 10 فإن الترجمة الإسبانية المناسبة هي " € 10" بدلاً من "10€" لكن التجريد عاجز عن تقديمها.
لإنشاء تجريد صحيح تُشحن جواهر I18n مع ميزة تسمى الاستيفاء المتغيّر (variable interpolation) الذي يسمح لك باستخدام المتغيّرات في تعريفات الترجمة وتمرير القيم لهذه المتغيّرات إلى تابع الترجمة.
يظهر التجريد الصحيح في المثال التالي:
# app/views/products/show.html.erb
<%= t('product_price', price: @product.price) %> # config/locales/en.yml en: product_price: "$%{price}" # config/locales/es.yml es: product_price: "%{price} €" |
تُتّخذ جميع قرارات النحو وعلامات الترقيم في التعريف نفسه وبالتالي يمكن أن يعطي التجريد ترجمة مناسبة.
الكلمات الرئيسية مثل default و scope محجوزة ولا يمكن استخدامها كأسماء للمتغيرات. وإن أستُخدمت يُرفَع استثناء I18n::ReservedInterpolationKey. إن توقّعت ترجمة متغير استيفاء (interpolation) ولكنه لم يُمرّر إلى translate# يُرفع استثناء I18n::MissingInterpolationArgument.
إضافة تنسيقات التاريخ / الوقت
حسنا! فلنقم الآن بإضافة طابع زمني إلى العرض حتى نتمكّن من عرض ميزة توطين التاريخ/الوقت أيضًا. لتوطين تنسيق الوقت تُمرّر كائن الوقت إلى I18n.l أو (يُفضّل) استخدام مساعد l#. يمكنك اختيار تنسيق عبر تمرير الخيار format: - يُستخدم التنسيق default: افتراضيًا.
# app/views/home/index.html.erb
<h1><%= t :hello_world %></h1> <p><%= flash[:notice] %></p> <p><%= l Time.now, format: :short %></p> |
وفي ملف ترجمات القراصنة فلنضف تنسيقًا زمنيًا (يوجد بالفعل في إعدادات ريلز الإفتراضيّة للغة الإنجليزية):
# config/locales/pirate.yml
pirate: time: formats: short: "arrrround %H'ish" |
بحيث يمنحك ذلك:
قد تحتاج الآن لإضافة المزيد من تنسيقات التاريخ/الوقت بالترتيب لجعل عمل الواجهة الخلفية I18n مثل المتوقّع (على الأقل بالنسبة للغة "القرصنة").
هناك طبعًا فرصة كبيرة أن شخصًا ما أنجز بالفعل كل هذا العمل عن طريق ترجمة إفتراضيّات ريلز للغتك المحليّة. راجع مستودع rails-i18n في GitHub لأرشيف من ملفات الإعدادات المحليّة المختلفة. عند وضع مثل هذا الملف (أو الملفّات) في المجلّد /config/locales، سيكون جاهزًا للاستخدام تلقائيًا.
قواعد انعطاف لإعدادات محليّة أخرى
يسمح لك ريلز بتحديد قواعد الإنعطاف (أي inflection مثل قواعد المُفرد والجمع) للإعدادات المحليّة غير الإنجليزية. يمكنك تعريف هذه القواعد لعدّة لغات في config/initializers/inflections.rb. يحتوي المُهيئ على مثال افتراضي لتحديد قواعد إضافيّة للغة الإنجليزية؛ اتبع ذاك التنسيق للغات أخرى كما تراه مناسبا.
العُروض المترجمة
فلنفترض أنك تمتلك وحدة تحكّم BooksController في تطبيقك. يُصيّر فعل الفهرس المحتوى في قالبapp/views/books/index.html.erb. عندما تضع المتغير المترجم من هذا القالب:
سيُصيّر ريلز المحتوى في هذا القالب عندما تُعيّن الإعدادات المحليّة على es: مع index.es.html.erb في نفس المجلّد. سيُستخدم العرض العادي index.html.erb عندما تُعيّن الإعدادات المحليّة على الإعدادات الإفتراضيّة. (قد تجلب إصدارات ريلز المستقبليّة هذا التوطين التلقائي للموارد في public إلخ.)
يمكنك الاستفادة من هذه الميزة عند العمل مع الكثير من المحتوى الثابت مثلًا، الذي سيكون من السذاجة لوضعه داخل قواميس YAML أو Ruby. مع ذلك ضع في الحسبان أن من اللازم نشر أي تغيير ترغب فيه بالنموذج لاحقًا لجميع القوالب.
تنظيم ملفّات الإعدادات المحليّة
عند استخدام SimpleStore الافتراضي المشحون مع مكتبة i18n تُخزّن القواميس في ملفات نصيّة عاديّة على القرص. قد تصعب إدارة الترجمة لجميع أجزاء تطبيقك في ملف واحد لكل لغة/إعداد محلّي. يمكنك تخزين هذه الملفات في تسلسل هرمي منطقي لك.
يمكن أن يبدو مجلّد config/locales كالتالي مثلًا:
|-defaults
|---es.rb |---en.rb |-models |---book |-----es.rb |-----en.rb |-views |---defaults |-----es.rb |-----en.rb |---books |-----es.rb |-----en.rb |---users |-----es.rb |-----en.rb |---navigation |-----es.rb |-----en.rb |
تستطيع بهذه الطريقة فصل أسماء سمات النموذج والنموذج من النص داخل العروض، وكل هذا من "الإعدادات الإفتراضيّة" (مثل تنسيقات التاريخ والوقت). يمكن أن توفّر المخازن الأخرى للمكتبة i18n وسائل مختلفة لهذا الفصل.
لا تُحمّل آلية تحميل الإعدادات المحليّة الإفتراضيّة في ريلز ملفّات الإعدادات المحليّة في القواميس المتداخلة كما هو الحال هنا. لذلك لكي يعمل هذا يجب أن نطلب من ريلز بوضوح أن ننظر أبعد من ذلك:
# config/application.rb
config.i18n.load_path += Dir[ريلز.root.join('config', 'locales', '**', '*.{rb,yml}')] |
نظرة عامة على ميزات API I18n
يجب أن يكون لديك فهم جيد لاستخدام المكتبة i18n الآن ومعرفة تمكّنك من فهم كيفية تدويل تطبيق ريلز بسيط. سوف نغطّي في الفصول التالية ميزاته بمزيد من العمق.
ستعرض هذه الفصول أمثلة باستخدام كل من تابع I18n.translate إضافة للتابع المساعد للعرض translate (مع ملاحظة الميزة الإضافية التي يوفّرها التابع المساعد للعرض).
من الميزات المغطّاة هنا:
- البحث عن الترجمات
- استيفاء البيانات في الترجمات (interpolating data into translations)
- جمع الترجمات
- استخدام ترجمة HTML آمنة ( تابع العرض المساعد)
- توطين التواريخ والأرقام والعملة وما إلى ذلك.
البحث عن الترجمات
البحث الأساسي، والنطاقات والمفاتيح المتداخلة (Basic Lookup, Scopes and Nested Keys)
يُبحث عن الترجمات بالمفاتيح والتي قد تكون رموزًا أو سلاسل بحيث يكون هذان النداءان متساويَين:
I18n.t :message
I18n.t 'message' |
يأخذ التابع translate أيضًا خيار scope: الذي قد يحتوي على مفتاح إضافي أو أكثر ستُسخدم لتحديد "مساحة اسم" أو نطاق لمفتاح الترجمة:
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] |
يبحث هذا عن الرسالة record_invalid: في رسائل خطأ السجلّات النشطة Active Record.
إضافة إلى ذلك يمكن تحديد المفتاح والنطاقات كمفاتيح مفصولة بنقط كما في:
I18n.translate "activerecord.errors.messages.record_invalid" |
وبالتالي فإن المكالمات التالية متكافئة:
I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord I18n.t :record_invalid, scope: 'activerecord.errors.messages' I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] |
الافتراضات (Defaults)
عندما يعطى خيار default: ستُردّ قيمته إن كانت الترجمة مفقودة:
I18n.t :missing, default: 'Not here'
# => 'Not here' |
إذا كانت القيمة default: رمزًا ستُترجم وتُستخدم كمفتاح. يمكن للمرء تقديم عدّة قيم كإعداد افتراضي. سيُردّ أول واحد منهم تنتج منه قيمة.
في ما يلي محاولة أولى لترجمة المفتاح missing: ثم المفتاح also_missing:. نظرًا لأن كليهما لا يسفر عن نتيجة ، فسترد السلسلة "ليس هنا":
I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here' |
البحث عن مجالات الأسماء والبحث بالجملة (ٍBulk and Namespace Lookup)
يمكن تمرير مصفوفة من المفاتيح للبحث عن عدّة ترجمات في آن واحد:
I18n.t [:odd, :even], scope: 'errors.messages'
# => ["must be odd", "must be even"] |
يمكن أيضًا ترجمة المفتاح كتجزئة (قد تكون متداخلة) للترجمات المجمّعة. يمكن للمرء مثلًا تلقّي جميع رسائل خطأ Active Record على أنها تجزئة مع:
I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... } |
البحث "الكسول"
يعرّف ريلز إستخدام طريقة ملائمة للبحث عن الإعداد المحلّي داخل العروض. عندما تمتلك القاموس التالي:
es:
books: index: title: "Título" |
يمكنك البحث عن القيمة books.index.title داخل القالب app/views/books/index.html.erb (لاحظ النقطة):
<%= t '.title' %> |
يتوفّر تحديد الترجمة التلقائية (Automatic translation scoping) جزئيًا فقط من تابع العرض المساعد translate.
يمكن أيضًا استخدام البحث "الكسول" في وحدات التحكّم:
en:
books: create: success: Book created! |
هذا مفيد في ضبط رسائل الفلاش مثلًا:
class BooksController < ApplicationController
def create # ... redirect_to books_url, notice: t('.success') end end |
صيغة الجمع
في العديد من اللغات - بما في ذلك اللغة الإنجليزية - هناك صيغتان فقط، وهما صيغة المفرد والجمع لسلسلة معينة ، مثل " 1 message" و "2 messages". ملك لغات أخرى ( العربية واليابانية والروسية وغيرها الكثير) قواعد نحويّة مختلفة تحتوي على صيَغ جمع أكثر أو أقل. وبالتالي توفّر واجهة برمجة التطبيقات (I18n) ميزة صيغ الجمع المرنة.
يملك متغير الإستيفاء count: (أي interpolation variable) دورًا خاصًّا من حيث أنه يُقحم بالترجمة ويُستخدم في اختيار صيغ جمع من الترجمات وفقًا لقواعد الجمع المحددّة في خلفيّة الجمع. بشكل افتراضي تُطبّق قواعد الجمع الإنجليزية فقط.
I18n.backend.store_translations :en, inbox: {
zero: 'no messages', # optional one: 'one message', other: '%{count} messages' } I18n.translate :inbox, count: 2 # => '2 messages' I18n.translate :inbox, count: 1 # => 'one message' I18n.translate :inbox, count: 0 # => 'no messages' |
خوارزميّة الإعداد المحلّي en: بسيطة مثل:
lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other entry[lookup_key] |
الترجمة المُرمّزة بالشكل one: تعتبر مفردة، و other: في الجمع. إذا كان العدد صفرًا ووُجد إدخال zero: سيُستخدم بدلاً من other:.
إن لم يرد البحث عن المفتاح تجزئة قابلة للجمع يُرفع استثناء I18n::InvalidPluralizationData.
القواعد الخاصة بإعداد محلّي (Locale-specific rules)
توفّر الجوهرة I18n خلفية صيغ جمع Pluralization التي يمكن استخدامها لتفعيل قواعد الخاصّة بإعدادات محليّة معيّنة. ضمّها إلى الخلفيّة البسيطة ثم أضف خوارزميات صبغ الجمع المُوطّنة إلى مخزن الترجمة كما مع i18n.plural.rule.
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } } I18n.backend.store_translations :pt, apples: { one: 'one or none', other: 'more than one' } I18n.t :apples, count: 0, locale: :pt # => 'one or none' |
بدلاً من ذلك يمكن استخدام الجوهرة المنفصلة rail-i18n لتوفير مجموعة كاملة من قواعد الجمع الفريدة بإعدادات محليّة معيّنة.
ضبط و تمرير إعداد محلّي
يمكن تعيين اللغة المحليّة إما على شكل I18n.locale (والذي يستخدم Thread.current مثل Time.zone مثلاً) أو يمكن تمريرها كخيار إلى translate# و localize#.
إذا لم يُمرّر أي إعداد محلّي يُستخدم I18n.locale:
I18n.locale = :de
I18n.t :foo I18n.l Time.now |
تمرير إعداد محلّي بشكل صريح:
I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de |
الافتراضي بالنسبة إلى I18n.locale هو I18n.default_locale الذي بدوره يعتبر en: كقيمته الافتراضيّة. يمكن ضبط الإعداد المحلّي الافتراضي على النحو التالي:
I18n.default_locale = :de |
استخدام ترجمات HTML آمنة
تُميّز المفاتيح التي تحتوي على لاحقة "html_" ومفاتيح المسمّاة "html" على أنها آمنة HTML. عند استخدامها في توابع العرض ، لن يتم الإفلات من HTML. عند استخدامها في العروض لن تُهرّب من HTML.
# config/locales/en.yml
en: welcome: <b>welcome!</b> hello_html: <b>hello!</b> title: html: <b>title!</b> # app/views/home/index.html.erb <div><%= t('welcome') %></div> <div><%= raw t('welcome') %></div> <div><%= t('hello_html') %></div> <div><%= t('title.html') %></div> |
الاستيفاء يهرب عند الحاجة رغم ذلك. خذ مثلًا:
en:
welcome_html: "<b>Welcome %{username}!</b>" |
يمكنك تمرير اسم المستخدم بأمان كما حدّده المستخدم:
<%# This is safe, it is going to be escaped if needed. %>
<%= t('welcome_html', username: @current_user.username) %> |
تُستوفى السلاسل آمنة من جهة أخرى كما هي حرفيًّا.
يتوفّر التحويل التلقائي إلى HTML آمن للترجمة النصيّة فقط من تابع العرض المساعد translate.
ترجمات لنماذج السجلّات النشطة
يمكنك استخدام التوابع Model.model_name.human و Model.human_attribute_name(attribute) للبحث عن التراجم بشفافية لأسماء نموذجك وسماتك.
عند إضافة الترجمات التالية مثلًا:
en:
activerecord: models: user: Dude attributes: user: login: "Handle" # will translate User attribute "login" as "Handle" |
ثم يرد User.model_name.human الإسم "Dude" و User.human_attribute_name("login") يرد "Handle".
يمكنك أيضًا تعيين صيغة الجمع لأسماء النماذج بإضافة ما يلي:
en:
activerecord: models: user: one: Dude other: Dudes |
ثم User.model_name.human(count: 2) سيرد "Dudes". أمّا مع count: 1 أو بدون params سيرد "Dude".
في حال إحتجت للوصول إلى سمات متداخلة داخل نموذج معيّن عليك تضمينها (nest) تحت model/attribute على مستوى النموذج من ملف ترجمتك:
en:
activerecord: attributes: user/gender: female: "Female" male: "Male" |
ثم سيرد User.human_attribute_name("gender.female") النتيجة "Female".
إذا كنت تستخدم صنفًا يتضمّن ActiveModel ولا يرث من ActiveRecord::Base، عوّض activerecord بـ activemodel في المسارات المفتاحيّة المذكورة أعلاه.
نطاقات رسالة الخطأ
يمكنك أيضًا ترجمة رسائل أخطاء التحقّق من السجل النشطة بسهولة. يمنحك Active Record بضع مجالات أسماء حيث يمكنك وضع ترجمات رسائلك من أجل توفير رسائل وترجمات مختلفة لبعض النماذج، والسمات ، و/أو تحقيقات الصحة (validations). كما يأخذ بشفرة توريث الجدول الواحد (single table inheritance) في الاعتبار بشفافيّة.
هذا يمنحك وسيلة قويّة للغاية لضبط رسائلك حسب احتياجات تطبيقك بمرونة.
ضع في اعتبارك نموذج مستخدم مع تحقّق من صحة سمة الاسم كما يلي:
class User < ApplicationRecord
validates :name, presence: true end |
المفتاح لرسالة الخطأ في هذه الحالة هو blank:. سيبحث Active Record عن هذا المفتاح في مجالات الأسماء:
activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name] activerecord.errors.messages errors.attributes.[attribute_name] errors.messages |
وبالتالي في مثالنا هذا، سيُجرّب المفاتيح التالية بهذا الترتيب ويرد النتيجة الأولى:
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank activerecord.errors.messages.blank errors.attributes.name.blank errors.messages.blank |
عندما تستخدم نماذجك إضافة على ذلك الميراث يُبحث عن الرسائل في سلسلة الميراث.
مثلًا قد يكون لديك نموذج مُشرف يرث من المستخدم:
class Admin < User
validates :name, presence: true end |
بعد ذلك سيبحث السجل النشط عن الرسائل بالترتيب التالي:
activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank activerecord.errors.models.user.attributes.name.blank activerecord.errors.models.user.blank activerecord.errors.messages.blank errors.attributes.name.blank errors.messages.blank |
وبهذه الطريقة يمكنك توفير ترجمات خاصة لرسائل خطأ مختلفة في نقاط مختلفة من سلسلة وراثة نماذجك وفي السمات أو النماذج أو النطاقات الإفتراضيّة.
رسالة خطأ الاستيفاء
دائمًا ما يكون اسم النموذج واسم السمة المترجمين والقيمة متاحين للإستيفاء كـ model و attribute و value على التوالي.
لهذا السبب بدلاً من رسالة الخطأ الإفتراضيّة "cannot be blank" مثلًا يمكنك استخدام اسم السمة كما يلي: "Please fill in your %{attribute}"
- يمكن استخدام count إن كان متاحًا لصيغة الجمع إن وُجدت:
// جدول 30 * 4 هنا //
ترجمة السجل النشط للمُساعد error_messages_for
إن كنت تستخدم مُساعد السجل النشط error_messages_for ستحتاج لإضافة ترجمات له.
يُشحن ريلز مع الترجمات التالية:
en:
activerecord: errors: template: header: one: "1 error prohibited this %{model} from being saved" other: "%{count} errors prohibited this %{model} from being saved" body: "There were problems with the following fields:" |
تحتاج لتثبيت جوهرة DynamicForm كي تستخدم هذا المساعد بإضافة هذا السطر إلى
Gemfile: gem 'dynamic_form' . |
الترجمات لموضوعات بريد إلكتروني مُرسل الأفعال Action Mailer
إن لم تقم بتمرير موضوع للتابع mail سيحاول Action Mailer العثور عليه في ترجماتك. سيستخدم البحث المُنفّذ النمط <mailer_scope>.<action_name>.subject لإنشاء المفتاح.
# user_mailer.rb
class UserMailer < ActionMailer::Base def welcome(user) #... end end en: user_mailer: welcome: subject: "Welcome to ريلز Guides!" |
لإرسال المعاملات للاستيفاء استخدم التابع default_i18n_subject على المُرسل (mailer).
# user_mailer.rb
class UserMailer < ActionMailer::Base def welcome(user) mail(to: user.email, subject: default_i18n_subject(user: user.name)) end end en: user_mailer: welcome: subject: "%{user}, welcome to ريلز Guides!" |
نظرة عامة على توابع مدمجة أخرى توفّر دعم I18n
يستخدم ريلز سلاسل ثابتة وتوطينات أخرى مثل سلاسل التنسيق ومعلومات تنسيق أخرى في بضعة مساعدين. فيما يلي نظرة عامة موجزة.
توابع Action View المساعدة
- distance_of_time_in_words تُترجم وتَجمَع نتائجها وتستوفي عدد الثواني والدقائق والساعات وما إلى ذلك. انظر ترجمات datetime.distance_in_words.
- يستخدم datetime_select و select_month أسماء الشهور المترجمة لملء وسم التحديد (select tag) الناتج. راجع date.month_names للترجمات. يبحث datetime_select أيضًا عن خيار الأمر من date.order (إلا إذا مرّرت الخيار صراحةً). يُترجم جميع مساعدي اختيار التواريخ (date selection helpers) العبارة باستخدام الترجمات في النطاق datetime.prompts إن أمكن.
- يستخدم المساعدون number_to_currency number_with_precision و number_to_percentage و number_with_delimiter و number_to_human_size و number_to_human_size إعدادات تنسيق الأرقام الموجودة في نطاق الأرقام.
توابع النموذج النشط
- يستخدم model_name.human و human_attribute_name الترجمات لأسماء نماذج وأسماء السمات إن توفّرت في نطاق activerecord.models. كما أنها تدعم ترجمة لأسماء الفئات الموروثة (مثلًا للاستخدام مع STI) كما هو موضح أعلاه في "نطاقات رسالة الخطأ".
- يضيف ActiveModel::Errors#full_messages اسم السمة إلى رسالة الخطأ باستخدام فاصل يُبحث عنه من error.format (وقيمته الافتراضيّة "%{attribute} %{message}" ).
توابع الدعم النشطة
- يستخدم Array#to_sentence إعدادات التنسيق كما أُعطيَت في النطاق support.array.
كيف تخزّن ترجماتك المخصّصة
تسمح لك الواجهة الخلفيّة البسيطة المشحونة مع Active Support بتخزين الترجمات بتنسيق ruby و YAML.
مثلًا يمكن أن يبدو شكل تجزئة Ruby تُقدّم ترجمات على النحو التالي:
{
pt: { foo: { bar: "baz" } } } |
سيبدو ملف YAML المكافئ كالتالي:
pt:
foo: bar: baz |
كما ترى، في كلتا الحالتين يكون مفتاح المستوى الأعلى هو اللغة المحليّة. foo: هو مفتاح مجال الاسم و bar: هو مفتاح لترجمة "baz".
في ما يلي مثال "حقيقي" من ملف ترجمة YAML للدعم النشط en.yml.
en:
date: formats: default: "%Y-%m-%d" short: "%b %d" long: "%B %d, %Y" |
لذلك سترد جميع عمليّات البحث الموافقة التالية تنسيق short: للتاريخ "b %d%":
I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date I18n.t :short, scope: 'date.formats' I18n.t :short, scope: [:date, :formats] |
نوصي بشكل عام باستخدام YAML كامتداد لتخزين الترجمات. مع ذلك هناك حالات حيث تريد تخزين Ruby lambdas كجزء من بيانات الإعدادات المحليّة. لتنسيقات التاريخ الخاصّة مثلًا.
بتخصيص إعدادك I18n
استخدام خلفيّات متنوّعة
لأسباب كثيرة كل ما تفعله الواجهة الخلفيّة البسيطة التي تُشحن مع Active Support هو "أبسط شيء يمكنه العمل" في Ruby on ريلز ... مما يعني أنها مضمونة العمل باللغة الإنجليزية فقط، ومع اللغات المشابهة جدا للغة الإنجليزية كتأثير جانبي. كما أن الواجهة الخلفيّة البسيطة قادرة على قراءة الترجمات فقط لا على تخزينها ديناميكيًا بأي شكل.
لكن هذا لا يعني أنك عالق مع هذه القيود. تُسهّل جوهرة I18n Ruby إستبدال تعريف إستخدام الخلفيّة البسيطة بشيء آخر يناسب احتياجاتك بشكل أفضل، من خلال تمرير نسخة خلفيّة إلى I18n.backend= setter.
على سبيل المثال، يمكنك استبدال الواجهة الخلفيّة البسيطة بخلفيّة السلسلة لسَلسَلة عدّة خلفيات معًا. يُفيدك هذا عندما تريد استخدام ترجمات قياسية بخلفيّة بسيطة مع تخزين ترجمات تطيق مخصّصة بقاعدة بيانات أو في الخلفيات الأخرى.
تستطيع مع الخلفيّة السلسلة استخدام خلفيّة السجل النشط Active Record والعودة إلى الخلفيّة (الافتراضيّة) البسيطة:
I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend) |
استخدام معالجات استثناء مختلفة
تحدّد واجهة برمجة التطبيقات I18n API الاستثناءات التالية التي ستُرفع من طرف الخلفيّات عند حدوث الظروف غير المتوقعة المقابلة:
MissingTranslationData # تعذر العثور على أي ترجمة للمفتاح المطلوب
InvalidLocale # nil غير صالح أي مثلًا I18n.locale الإعداد المحلّي المُعطى إلى InvalidPluralizationData # مُرّر خيار عددي لكن الترجمة غير مناسبة للجمع MissingInterpolationArgument # تتوقع الترجمة متغيّرًا وسيط مستوفى لم يُمرّر ReservedInterpolationKey # the translation contains a reserved interpolation variable name (i.e. one of: scope, default) UnknownFileType # I18n.load_path لا تعرف الخلفيّة كيفية التعامل مع نوع الملف الذي أُضيف إلى |
سيلتقط I18n API كل هذه الاستثناءات عندما تُطرح في الخلفيّة يُمرّرها إلى التابع default_exception_handler. سيعيد هذا التابع رفع كافّة الاستثناءات باستثناء استثناءات MissingTranslationData. عند إلتقاط استثناء MissingTranslationData ،سيرد سلسلة رسالة خطأ الاستثناء الذي يحتوي على المفتاح/النطاق المفقود.
يرجع السبب في ذلك إلى أنه أثناء التطوير عادة ما ترغب في تصيير عروضك حتى وإن كانت الترجمة مفقودة.
مع ذلك قد ترغب في سياقات أخرى بتغيير هذا السلوك. مثلًا لا تسمح معالجة الاستثناءات الإفتراضيّة بالتقاط الترجمات المفقودة أثناء الاختبارات الاليّة بسهولة. لهذا الغرض يمكن تحديد معالج استثناء مختلف. يجب أن يكون معالج الاستثناء المحدد تابعًا بوحدة I18n أو صنف مع تابع call#:
module I18n
class JustRaiseExceptionHandler < ExceptionHandler def call(exception, locale, key, options) if exception.is_a?(MissingTranslationData) raise exception.to_exception else super end end end end I18n.exception_handler = I18n::JustRaiseExceptionHandler.new |
سيعيد هذا رفع الاستثناء MissingTranslationData فقط، مع تمرير كل الإدخالات (input) الأخرى إلى معالج الاستثناء الافتراضي.
ولكن إن كنت تستخدم I18n::Backend::Pluralization سيرفع هذا المعالج أيضًا إستثناء
I18n::MissingTranslationData: translation missing: en.i18n.plural.rule |
الذي يجب تجاهله عادةً عودة إلى قاعدة صيغ الجمع الإفتراضيّة للغة الإنجليزية. تستطيع استخدام تحقّق (check) إضافي لمفتاح الترجمة لتجنّب هذا:
if exception.is_a?(MissingTranslationData) && key.to_s != 'i18n.plural.rule'
raise exception.to_exception else super end |
مثال آخر حيث السلوك الافتراضي غير مرغوب فيه هو TranslationHelper في ريلز الذي يوفر التابع t# (وكذلك #translate ). عند وقوع استثناء MissingTranslationData في هذا السياق يلف هذا المساعد الرسالة في نطاق باستخدام صنف CSS مسمّى translation_missing.
لفعل ذلك يجبر المساعد I18n#translate على رفع الاستثناءات بغض النظر عن معالج الاستثناء المُعرّف عن طريق تحديد الخيار raise: :
I18n.t :foo, raise: true # always re-raises exceptions from the backend |
ترجمة محتوى النموذج
واجهة برمجة تطبيقات I18n المُوصوفة في هذا الدليل تهدف بشكل أساسي لترجمة سلاسل الواجهة النصيّة. إن كنت تريد ترجمة محتوى نموذج (مثل مشاركات المدونّات) ستحتاج إلى حل مختلف للمساعدة على ذلك.
تستطيع عدّة جواهر المساعدة في ذلك:
- Globalize: تخزين الترجمات على جداول منفصلة للترجمة واحد لكل نموذج مترجم.
- Mobility: يوفّر الدعم لتخزين الترجمات بأشكال عديدة بما في ذلك جداول الترجمة وأعمدة json (في Postgres) ، إلخ.
- Traco: أعمدة قابلة للترجمة في ريلز 3 و 4 مخزّنة في جدول النموذج نفسه
الخلاصة
في هذه المرحلة، يجب أن يكون لديك نظرة عامة جيدة حول كيفية دعم I18n في Ruby on ريلز وأن تكون مستعدًا لبدء ترجمة مشروعك.
موارد
- مجموعة Google: rails-i18n - القائمة البريدية للمشروع.
- GitHub: rails-i18n - مستودع الشفرات ومتتبع إصدارات مشروع rails-i18n. الأهم من ذلك يمكنك العثور على الكثير من الترجمات على سبيل المثال لريلز التي ينبغي أن تعمل بتطبيقك في معظم الحالات.
- GitHub: i18n - مستودع الشيفرة ومتعقّب المشاكل لجوهرة i18n.
المؤلّفون
- Sven Fuchs (المؤلف الاول)
- Karel Minařík