الواجهة البرمجية للتدويل في ريلز
توفّر جوهرة روبي 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 (أو مسار الطلب).
ضبط الإعدادات المحليّة من معاملات العنوان 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" (أي centralizing dynamic decisions about the URLs) في 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 تلقائيًا، نظرًا لأن التصريح root to: "books#index"
في الملف 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
اختيار محلية مضمنة
عندما لا تعيّن المحلية بصراحة لطلب ما (عبر إحدى التوابع المذكورة أعلاه مثلًا)، يجب أن يحاول التطبيق استنتاج المحلية المرغوبة بنفسه.
استنباط المحلية من ترويسة اللغة
تشير الترويسة 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
من الضروري من الناحية العمليّة وجود تعليمات برمجيّة أكثر قوة لفعل هذا بذلك بشكل يعتمد عليه. مكتبة إيان هيكر (Iain Hecker) من أجل http_accept_language
أو برمجيّة رايان تومايكو (Ryan Tomayko) الوسيطة للمحلية مع Rack تقدّم حلولًا لهذه المشكلة.
استنباط المحلية من الموقع الجغرافي للعنوان IP
يمكن استخدام عنوان IP العميل الذي يقدم الطلب لاستنتاج منطقته وبالتالي إعداداته المحليّة. يمكن استخدام خدمات مثل GeoIP Lite Country أو جواهر مثل الجوهرة geocoder لتطبيق هذه الطريقة.
هذه الطريقة بشكل عام أقل موثوقية بكثير من استخدام ترويسة اللغة ولا يوصى بها لمعظم تطبيقات الويب.
تخزين الإعدادات المحلية من الجلسة أو من ملفات تعريف الارتباط (Cookies)
تحذير: قد تميل إلى تخزين الإعدادات المحليّة المختارة في جلسة عمل أو ملف تعريف ارتباط (Cookie) لكن لا تفعل هذا. يجب أن تكون المحليَّة واضحة وجزءًا من عنوان URL. هكذا لن تخالف افتراضات الناس الأساسيّة حول الويب نفسه: إن أرسلت عنوان URL إلى صديق، فيجب أن يشاهد نفس الصفحة والمحتوى الذي تراه. أفضل مصطلح لهذا الأمر هو أنك RESTful. اقرأ المزيد عن مقاربة RESTful في مقالات ستيفان تيلكوف (Stefan Tilkov). في بعض الأحيان، هناك استثناءات لهذه القاعدة وتلك التي سنناقشها أدناه.
التدويل والتوطين
حسنًا، الآن هيّئت الدعم I18n لتطبيق ريلز الخاص بك وأعلمته عن محليَّة المستخدم وكيفيّة الحفاظ عليه بين الطلبات.
نحتاج بعد ذلك لتدويل طلبنا عن طريق تجريد كل عنصر خاص بالمحليّة. وأخيرًا، نحتاج لتوطينهم بتوفير الترجمات اللازمة لهذه المُجرّدات.
في المثال التالي:
# config/routes.rb
Rails.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 إلى المحلية pirate (أي http://localhost:3000?locale=pirate)، فستصير السلسلتين النصيتين إلى ما يقابلهما لتلك المحلية:
ملاحظة: عليك إعادة تشغيل الخادم عند إضافة ملفّات جديدة للمحليّة.
يمكنك استخدام ملفات YAML (ذات الامتداد yml.) أو ملفات روبي العادية (ذات الامتداد rb.) لتخزين ترجماتك في SimpleStore. لكن YAML هو الخيار المفضّل بين مطورّي ريلز. ومع ذلك، لديه عيب واحد كبير هو أنَّ YAML حساس جدًا للمسافات البيضاء والمحارف الخاصّة، لذلك قد لا يُحمّل التطبيق القاموس بشكل صحيح. ستعطّل ملفّات روبي تطبيقك من الطلب الأول بحيث يمكنك العثور على الخطأ بسهولة. (إذا واجهت أي "مشكلات غريبة" مع قواميس YAML، حاول وضع الجزء ذي الصلة منها في ملف روبي.)
إن خُزّنت ترجماتك في ملفات 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>
وفي ملف الترجمات الخاص بالمحلية pirate، فلنضف تنسيقًا زمنيًا (يوجد بالفعل في إعدادات ريلز الإفتراضيّة للغة الإنجليزية):
# config/locales/pirate.yml
pirate:
time:
formats:
short: "arrrround %H'ish"
بحيث يمنحك ذلك:
تنبيه: قد تحتاج الآن لإضافة المزيد من تنسيقات التاريخ/الوقت بالترتيب لجعل عمل الواجهة الخلفية I18n مثل المتوقّع (على الأقل بالنسبة للغة "القرصنة").
هناك طبعًا فرصة كبيرة أن شخصًا ما أنجز بالفعل كل هذا العمل عن طريق ترجمة افتراضيّات ريلز للغتك المحليّة. راجع مستودع rails-i18n في GitHub لأرشيف من ملفات الإعدادات المحليّة المختلفة. عند وضع مثل هذا الملف (أو الملفّات) في المجلّد /config/locales، سيكون جاهزًا للاستخدام تلقائيًا.
قواعد انعطاف لإعدادات محليّة أخرى
يسمح لك ريلز بتحديد قواعد الإنعطاف (أي inflection مثل قواعد المُفرد والجمع) للإعدادات المحليّة غير الإنجليزية. يمكنك تعريف هذه القواعد لعدّة لغات في config/initializers/inflections.rb. يحتوي المُهيئ على مثال افتراضي لتحديد قواعد إضافيّة للغة الإنجليزية؛ اتبع ذاك التنسيق للغات أخرى كما تراه مناسبا.
العروض المترجمة
فلنفترض أنك تمتلك وحدة التحكّم BooksController
في تطبيقك. يُصيّر الإجراء index
المحتوى في القالب app/views/books/index.html.erb. عندما تضع المتغير المترجم من هذا القالب: index.es.html.erb، سيُصيّر ريلز المحتوى في هذا القالب عندما تُعيّن الإعدادات المحليّة إلى es:
. سيُستخدم العرض العادي index.html.erb عندما تُعيّن الإعدادات المحليّة إلى الإعدادات الافتراضيّة. (قد تجلب إصدارات ريلز المستقبليّة هذا التوطين التلقائي للموارد في public
...إلخ.)
يمكنك الاستفادة من هذه الميزة عند العمل مع الكثير من المحتوى الثابت؛ مثلًا، الذي سيكون من السذاجة لوضعه داخل قواميس YAML أو روبي. مع ذلك ضع في الحسبان أن من اللازم نشر أي تغيير ترغب فيه بالنموذج لاحقًا لجميع القوالب.
تنظيم ملفات المحلية
عند استخدام 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[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
نظرة عامة على ميزات الواجهة 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