شرح React: بناء تطبيق ترجمة نصوص PWA باستخدام LibreTranslate وSemantic UI

دقائق القراءة: 7

مقدمة: كيف تبني تطبيق ترجمة نصوص باستخدام React؟

إذا كنت تبحث عن مشروع عملي لتطوير مهاراتك في React، فإن إنشاء تطبيق ترجمة نصوص يُعد خياراً ممتازاً. في هذا الدليل، سنبني تطبيق PWA لترجمة النصوص بالاعتماد على LibreTranslate API، مع واجهة استخدام مرتبة عبر Semantic UI. التطبيق سيدعم عدداً كبيراً من اللغات، ويتيح ترجمة النص من لغة المصدر إلى اللغة الهدف بسهولة.

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

واجهة توضيحية لمشروع React لبناء تطبيق ترجمة نصوص بصيغة PWA

شكل التطبيق النهائي

بعد الانتهاء من تنفيذ المشروع، ستكون لديك واجهة تتضمن:

  • حقل إدخال للنص الأصلي.
  • قائمة منسدلة لاختيار اللغة المستهدفة.
  • حقل لإظهار النص المترجم.
  • زر لتنفيذ عملية الترجمة.

الشكل النهائي لتطبيق ترجمة النصوص باستخدام React

بناء واجهة المستخدم باستخدام Semantic UI

لتسريع تطوير الواجهة وتحسين مظهرها، سنستخدم مكتبة Semantic UI React. يمكنك زيارة الموقع الرسمي عبر الرابط التالي:

https://react.semantic-ui.com/

بعد الدخول إلى الموقع، انتقل إلى قسم Get Started لبدء التثبيت.

صفحة البدء في Semantic UI React لتثبيت المكتبة

تثبيت Semantic UI React

يمكنك تثبيت الحزمة باستخدام yarn أو npm:

$ yarn add semantic-ui-react semantic-ui-css
## Or
$ npm install semantic-ui-react semantic-ui-css

بعد اكتمال التثبيت، استورد ملف التنسيق داخل index.js:

import 'semantic-ui-css/semantic.min.css'

بهذا تصبح المكتبة جاهزة للاستخدام داخل المشروع.

إنشاء مكوّن Translate في React

سننشئ مكوّناً رئيسياً باسم Translate يحتوي على عناصر التطبيق الأساسية. البداية ستكون بإضافة عنوان واضح للتطبيق.

import React from 'react';

export default function Translate() {
  return (
    <div>
      <div className="app-header">
        <h2 className="header">Texty Translator</h2>
      </div>
    </div>
  )
}

تنسيق العنوان باستخدام CSS

@import url('https://fonts.googleapis.com/css2?family=Azeret+Mono&display=swap');

.app-header {
  text-align: center;
  padding: 20px;
}

.header {
  font-family: 'Azeret Mono', monospace;
  font-size: 30px;
}

في هذا التنسيق، استخدمنا خط Azeret Mono من Google Fonts مع محاذاة العنوان في المنتصف وإضافة مسافة داخلية مناسبة.

عنوان تطبيق Texty Translator بعد تنسيقه في React

إضافة عناصر الإدخال والترجمة

نحتاج الآن إلى أربعة عناصر رئيسية داخل الواجهة:

  1. حقل لإدخال النص المراد ترجمته.
  2. قائمة منسدلة لاختيار اللغة.
  3. حقل لعرض الترجمة الناتجة.
  4. زر لتنفيذ الترجمة.

استورد المكوّنات اللازمة من semantic-ui-react بالشكل التالي:

import { Form, TextArea, Button, Icon } from 'semantic-ui-react';

ثم أضف الهيكل الأساسي للواجهة داخل المكوّن:

import React from 'react';
import { Form, TextArea, Button, Icon } from 'semantic-ui-react';

export default function Translate() {
  return (
    <div>
      <div className="app-header">
        <h2 className="header">Texty Translator</h2>
      </div>

      <div className='app-body'>
        <div>
          <Form>
            <Form.Field control={TextArea} placeholder='Type Text to Translate..' />
            <select className="language-select">
              <option>Please Select Language..</option>
            </select>
            <Form.Field control={TextArea} placeholder='Your Result Translation..' />
            <Button color="orange" size="large">
              <Icon name='translate' />
              Translate
            </Button>
          </Form>
        </div>
      </div>
    </div>
  )
}

تنسيق هيكل التطبيق

@import url('https://fonts.googleapis.com/css2?family=Azeret+Mono&display=swap');

.app-header {
  text-align: center;
  padding: 20px;
}

.header {
  font-family: 'Azeret Mono', monospace;
  font-size: 30px;
}

.app-body {
  padding: 20px;
  text-align: center;
}

.language-select {
  height: 40px !important;
  margin-bottom: 15px;
  outline: none !important;
}

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

واجهة تطبيق الترجمة بعد إضافة حقول الإدخال وزر الترجمة

إعداد API الخاصة بالترجمة

لكي يعمل التطبيق فعلياً، سنعتمد على LibreTranslate API، وهي خدمة مناسبة لترجمة النصوص واكتشاف اللغة المصدر وجلب اللغات المدعومة.

واجهة LibreTranslate API التي تعرض نقاط النهاية المتاحة للترجمة واكتشاف اللغة

سنستخدم ثلاث نقاط نهاية رئيسية:

  • /detect لاكتشاف لغة النص المُدخل.
  • /languages لجلب اللغات المدعومة.
  • /translate لتنفيذ الترجمة النهائية.

تثبيت Axios

لإرسال الطلبات إلى API بطريقة سهلة، سنستخدم مكتبة Axios.

yarn add axios
##OR
npm i axios

بعد التثبيت، استوردها داخل المكوّن:

import axios from 'axios';

سنحتاج أيضاً إلى useState وuseEffect من React:

import React, { useState, useEffect } from 'react';

تخزين النص المُدخل داخل state

أول خطوة منطقية هي حفظ النص الذي يكتبه المستخدم داخل state.

const [inputText, setInputText] = useState('');

ثم اربط حقل الإدخال بحدث onChange:

<Form.Field
  control={TextArea}
  placeholder='Type Text to Translate..'
  onChange={(e) => setInputText(e.target.value)}
/>

بهذا سيتم تخزين كل تغيير في النص داخل المتغير inputText.

اكتشاف لغة النص المصدر عبر API

قبل تنفيذ الترجمة، يجب معرفة لغة النص المدخل. لهذا سننشئ دالة باسم getLanguageSource():

const getLanguageSource = () => {
  axios.post(`https://libretranslate.de/detect`, { q: inputText })
    .then((response) => {
      console.log(response.data[0].language)
    })
}

ترسل هذه الدالة النص داخل الخاصية q، ثم تستخرج رمز اللغة المكتشفة من الاستجابة.

يمكنك ربط الدالة بزر الترجمة للتجربة الأولية:

<Button color="orange" size="large" onClick={getLanguageSource}>
  <Icon name='translate' />
  Translate
</Button>

نتيجة اكتشاف لغة النص داخل وحدة التحكم في المتصفح

حفظ رمز اللغة المكتشفة

بدلاً من طباعة اللغة في وحدة التحكم فقط، من الأفضل تخزينها داخل state:

const [detectLanguageKey, setdetectedLanguageKey] = useState('');
const getLanguageSource = () => {
  axios.post(`https://libretranslate.de/detect`, { q: inputText })
    .then((response) => {
      setdetectedLanguageKey(response.data[0].language)
    })
}

نستخدم العنصر الأول من المصفوفة response.data[0] لأن بيانات الكشف تبدأ منه.

import React, { useState, useEffect } from 'react';
import { Form, TextArea, Button, Icon } from 'semantic-ui-react';
import axios from 'axios';

export default function Translate() {
  const [inputText, setInputText] = useState('');
  const [detectLanguageKey, setdetectedLanguageKey] = useState('')

  const getLanguageSource = () => {
    axios.post(`https://libretranslate.de/detect`, { q: inputText })
      .then((response) => {
        setdetectedLanguageKey(response.data[0].language)
      })
  }

  return (
    <div>
      <div className="app-header">
        <h2 className="header">Texty Translator</h2>
      </div>
      <div className='app-body'>
        <div>
          <Form>
            <Form.Field control={TextArea} placeholder='Type Text to Translate..' onChange={(e) => setInputText(e.target.value)} />
            <select className="language-select">
              <option>Please Select Language..</option>
            </select>
            <Form.Field control={TextArea} placeholder='Your Result Translation..' />
            <Button color="orange" size="large" onClick={getLanguageSource}>
              <Icon name='translate' />
              Translate
            </Button>
          </Form>
        </div>
      </div>
    </div>
  )
}

جلب اللغات المدعومة وعرضها في القائمة المنسدلة

الآن نحتاج إلى جلب قائمة اللغات التي يدعمها LibreTranslate، ثم عرضها داخل عنصر select.

useEffect(() => {
  axios.get(`https://libretranslate.de/languages`)
    .then((response) => {
      console.log(response.data)
    })
}, [])

تعمل useEffect هنا مرة واحدة عند تحميل المكوّن، لأن مصفوفة الاعتماد فارغة.

قائمة اللغات المدعومة من LibreTranslate داخل وحدة التحكم

تخزين قائمة اللغات في state

const [languagesList, setLanguagesList] = useState([])
useEffect(() => {
  axios.get(`https://libretranslate.de/languages`)
    .then((response) => {
      setLanguagesList(response.data)
    })
}, [])

عرض اللغات داخل select

<select className="language-select">
  <option>Please Select Language..</option>
  {languagesList.map((language) => {
    return (
      <option value={language.code}>
        {language.name}
      </option>
    )
  })}
</select>

القائمة المنسدلة التي تعرض اللغات المدعومة في تطبيق React

التقاط رمز اللغة المختارة

بعد عرض اللغات، نحتاج إلى معرفة اللغة التي يختارها المستخدم حتى نستخدم رمزها في طلب الترجمة.

إنشاء state لحفظ اللغة الهدف

const [selectedLanguageKey, setLanguageKey] = useState('')

إنشاء دالة languageKey

const languageKey = (selectedLanguage) => {
  setLanguageKey(selectedLanguage.target.value)
}

ثم اربطها بحدث onChange داخل القائمة المنسدلة:

<select className="language-select" onChange={languageKey}>
  <option>Please Select Language..</option>
  {languagesList.map((language) => {
    return (
      <option value={language.code}>
        {language.name}
      </option>
    )
  })}
</select>

بهذا أصبح لدينا الآن ثلاث قيم رئيسية مطلوبة لتنفيذ الترجمة:

  • النص الأصلي: inputText
  • لغة المصدر المكتشفة: detectLanguageKey
  • لغة الهدف المختارة: selectedLanguageKey

توثيق LibreTranslate الذي يوضح مدخلات API الخاصة بالترجمة

تنفيذ API الترجمة النهائية

الخطوة الأخيرة هي إرسال البيانات إلى نقطة النهاية /translate واستقبال النص المترجم.

إنشاء state لحفظ نتيجة الترجمة

const [resultText, setResultText] = useState('');

إنشاء دالة translateText

const translateText = () => {
  getLanguageSource();
  let data = {
    q: inputText,
    source: detectLanguageKey,
    target: selectedLanguageKey
  }

  axios.post(`https://libretranslate.de/translate`, data)
    .then((response) => {
      setResultText(response.data.translatedText)
    })
}

داخل الكائن data نرسل:

  • q: النص المراد ترجمته.
  • source: رمز لغة المصدر.
  • target: رمز اللغة الهدف.

ثم نخزن الترجمة القادمة من الاستجابة داخل resultText.

ربط الزر بدالة الترجمة

<Button color="orange" size="large" onClick={translateText}>
  <Icon name='translate' />
  Translate
</Button>

إظهار النتيجة داخل مربع النص الثاني

<Form.Field control={TextArea} placeholder='Your Result Translation..' value={resultText} />

يمكنك الآن كتابة النص، ثم اختيار اللغة، والضغط على الزر للحصول على الترجمة.

import React, { useState, useEffect } from 'react';
import { Form, TextArea, Button, Icon } from 'semantic-ui-react';
import axios from 'axios';

export default function Translate() {
  const [inputText, setInputText] = useState('');
  const [detectLanguageKey, setdetectedLanguageKey] = useState('');
  const [selectedLanguageKey, setLanguageKey] = useState('')
  const [languagesList, setLanguagesList] = useState([])
  const [resultText, setResultText] = useState('');

  const getLanguageSource = () => {
    axios.post(`https://libretranslate.de/detect`, { q: inputText })
      .then((response) => {
        setdetectedLanguageKey(response.data[0].language)
      })
  }

  useEffect(() => {
    axios.get(`https://libretranslate.de/languages`)
      .then((response) => {
        setLanguagesList(response.data)
      })
  }, [])

  const languageKey = (selectedLanguage) => {
    setLanguageKey(selectedLanguage.target.value)
  }

  const translateText = () => {
    getLanguageSource();
    let data = {
      q: inputText,
      source: detectLanguageKey,
      target: selectedLanguageKey
    }

    axios.post(`https://libretranslate.de/translate`, data)
      .then((response) => {
        setResultText(response.data.translatedText)
      })
  }

  return (
    <div>
      <div className="app-header">
        <h2 className="header">Texty Translator</h2>
      </div>
      <div className='app-body'>
        <div>
          <Form>
            <Form.Field control={TextArea} placeholder='Type Text to Translate..' onChange={(e) => setInputText(e.target.value)} />
            <select className="language-select" onChange={languageKey}>
              <option>Please Select Language..</option>
              {languagesList.map((language) => {
                return (
                  <option value={language.code}>
                    {language.name}
                  </option>
                )
              })}
            </select>
            <Form.Field control={TextArea} placeholder='Your Result Translation..' value={resultText} />
            <Button color="orange" size="large" onClick={translateText}>
              <Icon name='translate' />
              Translate
            </Button>
          </Form>
        </div>
      </div>
    </div>
  )
}

تحسين اكتشاف اللغة تلقائياً عند تغير النص

بدلاً من استدعاء دالة اكتشاف اللغة يدوياً فقط عند الضغط على زر الترجمة، يمكن تحسين السلوك عبر تشغيلها كلما تغير inputText.

useEffect(() => {
  axios.get(`https://libretranslate.de/languages`)
    .then((response) => {
      setLanguagesList(response.data)
    })

  getLanguageSource()
}, [inputText])

هذا يعني أن React ستعيد تنفيذ useEffect كلما تغيّر النص المدخل، وبالتالي سيتم تحديث لغة المصدر تلقائياً.

النتيجة النهائية لتطبيق ترجمة النصوص بعد تنفيذ الترجمة في React

ملاحظات مهمة لتحسين المشروع

رغم أن التطبيق يعمل بالشكل المطلوب، هناك بعض التحسينات التقنية التي يُستحسن تنفيذها في المشاريع الواقعية:

  • إضافة loading state أثناء انتظار نتائج API.
  • معالجة الأخطاء باستخدام catch لعرض رسالة مناسبة عند فشل الطلب.
  • منع إرسال طلب ترجمة إذا كان النص فارغاً أو لم تُحدّد اللغة الهدف.
  • إضافة خاصية debounce لتقليل عدد طلبات اكتشاف اللغة أثناء الكتابة.
  • تحويل المشروع إلى PWA كامل مع دعم العمل الجزئي دون اتصال.

خاتمة

بهذا أصبحت تعرف كيفية بناء تطبيق ترجمة نصوص باستخدام React، مع الاستفادة من Semantic UI لإنشاء واجهة نظيفة، وAxios للتعامل مع LibreTranslate API. هذا النوع من المشاريع ممتاز للتدريب على إدارة state، وربط الواجهة بالخدمات الخارجية، والتعامل مع النماذج والتفاعلات في React.

إذا أردت تطوير المشروع أكثر، يمكنك إضافة سجل للترجمات السابقة، أو زر لتبديل اللغتين، أو دعم النسخ الصوتي والترجمة الفورية.

الخلاصة التقنية

من الناحية التقنية، المشروع يُعد مثالاً جيداً على ربط React مع خدمات خارجية بطريقة عملية. لكن من الأفضل في النسخة الإنتاجية إعادة تنظيم منطق الترجمة بحيث يتم انتظار نتيجة اكتشاف اللغة قبل إرسال طلب /translate، لأن تحديث state في React غير متزامن. كما أن فصل منطق API في ملف مستقل سيجعل الكود أكثر قابلية للصيانة والتوسع مستقبلاً.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *