الواجهة البرمجية للتدويل في ريلز

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث

توفّر جوهرة روبي I18n (اختزالٌ للمصطلح "التدويل" [internationalization]) التي تُشحن مع ريلز (بدءًا من الإصدار 2.2) إطارًا سهل الاستخدام وقابلًا للتوسعة لترجمة تطبيقك إلى لغة مخصصّة واحدة بخلاف الإنجليزية أو لتوفير دعم بلغات متعدّدة في تطبيقك.

عادةً ما تعني عمليّة "التدويل" تجريد كل السلاسل النصية والبتات الخاصة الأخرى ذات العلاقة باللغة (مثل تنسيقات التاريخ أو العملة) من تطبيقك. تعني عملية "التوطين" توفير الترجمات والصيغ المحلية لهذه البتات.

لذلك، يجب عليك في عمليّة تدويل تطبيقك ريلز:

  • التأكّد من دعمك للواجهة البرمجية i18n.
  • إخبار ريلز بمكان العثور على قواميس اللغة.
  • إخبار ريلز بكيفيّة إعداد اللغة المحليّة والمحافظة عليها وتبديلها.

في عملية توطين تطبيقك، سترغب غالبًا بالقيام بالأشياء الثلاثة التالية:

  • استبدال أو استكمال الإعدادات الافتراضية لريلز؛ على سبيل المثال، تنسيقات التاريخ والوقت، وأسماء الشهور، وأسماء نماذج Active Record، ...إلخ.
  • تجريد السلاسل النصيّة في تطبيقك إلى قواميس مفتاحي؛ مثلًا الرسائل الوامضة (flash messages)، والنص الثابت في عروضك، ...إلخ.
  • تخزين القواميس الناتجة في مكان ما.

سيرشدك هذا الدليل إلى استعمال الواجهة البرمجية I18n كما سيعلمك كيفيّة تدويل تطبيق ريلز من البداية.

ستتعلم بعد قراءة هذا الدليل:

  • كيف تعمل الواجهة البرمجية I18n في ريلز.
  • كيفيّة استخدام I18n بشكل صحيح في تطبيق RESTful بطرق مختلفة.
  • كيفيّة استخدام I18n لترجمة أخطاء Active Record أو مواضيع البريد الإلكتروني Action Mailer.
  • بعض الأدوات الأخرى لمواصلة ترجمة تطبيقك.

ملاحظة: يوفّر لك إطار روبي I18n جميع الوسائل اللازمة لتدويل / توطين تطبيقك ريلز. يمكنك أيضًا استخدام مختلف الجواهر المتاحة لإضافة وظائف أو ميزات إضافية. انظر جوهرة ريلز i18n لمزيد من المعلومات.

كيفية عمل الواجهة البرمجية I18n في ريلز

التدويل مشكلة معقدة. تختلف اللغات الطبيعية بطرق كثيرة (مثلًا في قواعد الجمع) بحيث يصعب توفير أدوات لحل جميع المشاكل في وقت واحد. لهذا السبب، تركّز الواجهة I18n البرمجية في ريلز على:

  • تقديم الدعم للغة الإنجليزية واللغات المماثلة مباشرة.
  • تسهيل تخصيص كل شيء للغات الأخرى وتوسيعها.

دُوّلت (internationalized) كل سلسلة نصية ثابتة في إطار ريلز كجزء من هذا الحل - مثل رسائل التحقق من Active Record، وتنسيقات الوقت والتاريخ. يعني توطين تطبيق ريلز تعريف القيم المترجمة لهذه السلاسل في اللغات المطلوبة.

لتوطين وتخزين وتحديث المحتوى في تطبيقك (ترجمة منشورات مدوّنة مثلًا)، راجع قسم ترجمة محتوى النماذج.

المعمارية العامة للمكتبة

تُقسّم جوهرة روبي I18n إلى قسمين:

  • واجهة برمجة التطبيقات العامة لإطار عمل i18n - وحدة روبي مع التوابع العامّة التي تحدد كيّفية عمل المكتبة.
  • واجهة خلفية افتراضيّة (والتي تسمى عمدًا الواجهة الخلفية Simple "الواجهة الخلفية البسيطة") تُنفّذ هذه التوابع.

بصفتك مستخدمًا، عليك دائمًا الوصول إلى التوابع العامّة عبر الوحدة I18n فقط، ولكن من المفيد معرفة قدرات الواجهة الخلفية.

من الممكن تبديل الواجهة الخلفية Simple التي شُحنت بأخرى أكثر فعاليّة، والتي ستخزن بيانات الترجمة في قاعدة بيانات علائقيّة، أو قاموس 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". تُدوّل كل سلسلة داخل ريلز بهذه الطريقة؛ انظر على سبيل المثال لرسائل التحقق من صحّة Active Model في الملف activemodel/lib/active_model/locale/en.yml أو تنسيقات الوقت والتاريخ في الملف activesupport/lib/active_support/locale/en.yml. يمكنك استخدام YAML أو جداول Hash القياسيّة لتخزين الترجمات في الواجهة الخلفية الافتراضية (الواجهة الخلفية Simple).

ستستخدم مكتبة 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، وتصيّر الاستجابة السلاسل النصية باللغة الإنجليزية:

ظهور السلسلتين النصيتين باللغة الإنجليزية لأن المحلية الافتراضية لم تتغير وهي en:.

إن عيّنت المحليّة عبر عنوان URL إلى المحلية pirate (أي http://localhost:3000?locale=pirate)، فستصير السلسلتين النصيتين إلى ما يقابلهما لتلك المحلية:

تغيّر السلاسل النصية بتغير المحلية إلى 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 آمنة ( تابع العرض المساعد)
  • توطين التواريخ والأرقام والعملة وما إلى ذلك

البحث عن الترجمات

البحث الأساسي، والنطاقات والمفاتيح المتداخلة

يُبحث عن الترجمات بالمفاتيح والتي قد تكون رموزًا أو سلاسل بحيث يكون هذان النداءان متساويَين:

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:. نظرًا لأن كليهما لا يسفر عن نتيجة، فستعاد السلسلة "Not here":

I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'

البحث عن مجالات الأسماء والبحث بالجملة

يمكن تمرير مصفوفة من المفاتيح للبحث عن عدّة ترجمات في آن واحد:

I18n.t [:odd, :even], scope: 'errors.messages'
# => ["must be odd", "must be even"]

يمكن أيضًا ترجمة المفتاح إلى جدول Hash من الترجمات المجمّعة (قد تكون متداخلة). يمكن للمرء مثلًا تلقّي جميع رسائل خطأ Active Record على أنها جدول Hash مع:

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: تعد صيغة جمع. إذا كان count صفرًا ووُجد إدخال zero:، سيُستخدَم بدلًا من other:.

إن لم يعيد البحث عن المفتاح جدول Hash قابل للجمع، يُرفَع الاستثناء I18n::InvalidPluralizationData.

القواعد الخاصة بمحليَّة (Locale-specific rules)

توفّر الجوهرة I18n خلفية صيغ جمع (Pluralization backend) التي يمكن استخدامها لتفعيل قواعد خاصّة بإعدادات محليّة معيّنة. ضمنها إلى الخلفيّة Simple ثم أضف خوارزميات صيغ الجمع المُوطّنة إلى مخزن الترجمة كما مع 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.

# 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.

كيفية عرض السلاسل الآمنة وتفسيرها إلى شيفرة HTML وعدمه.
كيفية عرض السلاسل الآمنة وتفسيرها إلى شيفرة HTML وعدمه.

ترجمات لنماذج Active Record

يمكنك استخدام التابعين 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 بسهولة. يمنحك Active Record بضع مجالات أسماء حيث يمكنك وضع ترجمات رسائلك من أجل توفير رسائل وترجمات مختلفة لبعض النماذج، والخاصيات، و/أو تحقيقات الصحة (validations). كما يأخذ شفيرة توريث الجدول الواحد (single table inheritance) في الحسبان بشفافيّة.

هذا يمنحك وسيلة قويّة للغاية لضبط رسائلك حسب احتياجات تطبيقك بمرونة.

ضع في اعتبارك النموذج User مع التحقّق من صحة الخاصية name كما يلي:

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

عندما تستخدم نماذجك إضافة على ذلك الميراث، يُبحث عن الرسائل في سلسلة الميراث. مثلًا، قد يكون لديك النموذج Admin يرث من User:

class Admin < User
  validates :name, presence: true
end

بعد ذلك، سيبحث Active Record عن الرسائل بالترتيب التالي:

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 إن كان متاحًا لصيغة الجمع إن وُجدت:
التحقق مع الخيار الرسالة الاستيفاء
تأكيد - ‎:confirmation attribute
قبول - ‎:accepted -
وجود - ‎:blank -
غياب - ‎:present -
طول :within, :in ‎:too_short count
طول ‎:within, :in ‎:too_long count
طول ‎:is ‎:wrong_length count
طول ‎:minimum ‎:too_short count
طول ‎:maximum ‎:too_long count
فردية - ‎:taken -
تنسيق (format) - ‎‎:invalid -
الشمول (inclusion) - ‎:inclusion -
الاستثناء (exclusion) - ‎:exclusion -
الترابط (associated) - ‎:invalid -
الارتباط غير الاختياري (non-optional association) - ‎:required -
عدد (Numericality) - ‎:not_a_number -
عدد ‎:greater_than ‎:greater_than count
عدد ‎:greater_than_or_equal_to ‎:greater_than_or_equal_to count
عدد ‎:equal_to ‎:equal_to count
عدد ‎:less_than ‎:less_than count
عدد ‎:less_than_or_equal_to ‎:less_than_or_equal_to count
عدد ‎:other_than ‎:other_than count
عدد ‎:only_integer ‎:not_an_integer -
عدد ‎:odd ‎:odd -
عدد ‎:even ‎:even -

ترجمات للمساعد error_messages_for الذي يخص Active Record

إن كنت تستخدم المساعد error_messages_for الذي يخص Active Record، ستحتاج لإضافة ترجمات له.

يُشحن ريلز مع الترجمات التالية:

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 Rails 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 Rails Guides!"

نظرة عامة على توابع مدمجة أخرى توفر دعم I18n

يستخدم ريلز سلاسل نصية ثابتة وتوطينات أخرى مثل سلاسل التنسيق ومعلومات تنسيق أخرى في بضعة مساعدين. فيما يلي نظرة عامة موجزة.

توابع مساعدة للإجراء View

  • distance_of_time_in_words: يُترجم ويُجمَع نتائجه ويستوفي عدد الثواني والدقائق والساعات وما إلى ذلك. انظر ترجمات datetime.distance_in_words.
  • datetime_select و select_month: يستخدمان أسماء الشهور المترجمة لملء الوسم <select> الناتج. راجع date.month_names للترجمات. يبحث datetime_select أيضًا عن الخيار order من 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: تستخدم إعدادات تنسيق الأرقام الموجودة في نطاق number.

توابع الإجراء Model

  • model_name.human و human_attribute_name: يستخدمان الترجمات لأسماء نماذج وأسماء الخاصيات إن توفّرت في النطاق activerecord.models. كما أنها تدعم ترجمة لأسماء الأصناف الموروثة (مثلًا للاستخدام مع STI) كما هو موضح أعلاه في القسم "مجالات رسالة الخطأ".
  • ActiveModel::Errors.generate_message: (الذي يُستخدَم من قبل تحققات Active Model ولكن قد يُستخدَم يدويًّا) يستخدم model_name.human و human_attribute_name (انظر أعلاه). يترجم أيضًا رسالة الخطأ ويدعم الترجمات من أجل أسماء الأصناف الموروثة كما شُرِح أعلاه في القسم "مجالات رسالة الخطأ".
  • ActiveModel::Errors.full_messages: يضيف اسم الخاصية إلى رسالة الخطأ باستخدام فاصل يُبحَث عنه من error.format (وقيمته الافتراضيّة "‎%{attribute} %{message}‎").

توابع Active Support

  • Array.to_sentence: يستخدم إعدادات التنسيق كما أُعطيَت في النطاق support.array.

كيف تخزن ترجماتك المخصصة

تسمح لك الواجهة الخلفيّة Simple المشحونة مع Active Support بتخزين الترجمات بتنسيق ruby ​​و YAML.

مثلًا، يمكن أن يبدو شكل جدول Hash يوفر ترجمات على النحو التالي:

{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

سيبدو ملف YAML المكافئ كالتالي:

pt:
  foo:
    bar: baz

كما ترى، في كلتا الحالتين، يكون مفتاح المستوى الأعلى هو المحليّة. foo: هو مفتاح مجال الاسم و bar: هو مفتاح للترجمة "baz". في ما يلي مثال "حقيقي" من ملف YAML لترجمات Active Support (الملف 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 كامتداد لتخزين الترجمات. مع ذلك، هناك حالات تريد تخزين تعابير lambda لروبي كجزء من بيانات المحليّة مثل من أجل تنسيقات التاريخ الخاصّة.

تخصيص إعداد I18n الخاص بك

استخدام واجهات خلفية متنوعة

لأسباب كثيرة، كل ما تفعله الواجهة الخلفيّة Simple التي تُشحن مع Active Support هو "أبسط شيء يمكنه العمل" في ريلز مما يعني أنها مضمونة العمل باللغة الإنجليزية فقط، ومع اللغات المشابهة جدًا للغة الإنجليزية كتأثير جانبي. كما أن الواجهة الخلفيّة Simple قادرة على قراءة الترجمات فقط لا على تخزينها ديناميكيًا بأي شكل.

لكن هذا لا يعني أنك عالق مع هذه القيود. تُسهّل جوهرة I18n لروبي استبدال تعريف استخدام الواجهة الخلفية Simple بشيء آخر يناسب احتياجاتك بشكل أفضل من خلال تمرير نسخة خلفيّة إلى I18n.backend= setter.

على سبيل المثال، يمكنك استبدال الواجهة الخلفيّة Simple مع الواجهة الخلفية Chain لسَلسَلة عدّة واجهات خلفية معًا. يُفيدك هذا عندما تريد استخدام ترجمات قياسية مع الواجهة الخلفية Simple ولكن مع تخزين ترجمات تطبيق مخصّصة بقاعدة بيانات أو في الواجهات الخلفية الأخرى.

تستطيع مع الواجهة الخلفية Chain استخدام الواجهة الخلفية Active Record والعودة إلى الواجهة الخلفية Simple (الافتراضيّة):

I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)

استخدام معالجات مختلفة للاستثناءات

تحدّد الواجهة البرمجية I18n الاستثناءات التالية التي ستُرفع من طرف الواجهات الخلفية عند حدوث الظروف غير المتوقعة المقابلة:

# تعذر العثور على أي ترجمة للمفتاح المطلوب
MissingTranslationData

#  nil غير صالحة أي مثلًا I18n.locale المحليَّة المعطاة إلى
InvalidLocale

# مُرِّر خيار عددي لكن الترجمة غير مناسبة للجمع
InvalidPluralizationData

# تتوقع الترجمة وسيط استيفاء لم يُمرّر
MissingInterpolationArgument 

# تحوي الترجمة اسم متغير استيفاء محجوز
ReservedInterpolationKey

# I18n.load_path لا تعرف الواجهة الخلفية كيفية التعامل مع نوع الملف الذي أُضيف إلى
UnknownFileType            

ستلتقط الواجهة البرمجية I18n كل هذه الاستثناءات عندما تُرمَى في الخلفيّة وتمرّرها إلى التابع 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 في هذا السياق يلف هذا المساعد الرسالة في عنصر <span> باستخدام صنف 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 في ريلز وأن تكون مستعدًا لبدء ترجمة ‎مشروعك عبره.

مصادر مرجعية

المؤلفون

مصادر