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

شكل التطبيق النهائي
بعد الانتهاء من تنفيذ المشروع، ستكون لديك واجهة تتضمن:
- حقل إدخال للنص الأصلي.
- قائمة منسدلة لاختيار اللغة المستهدفة.
- حقل لإظهار النص المترجم.
- زر لتنفيذ عملية الترجمة.

بناء واجهة المستخدم باستخدام Semantic UI
لتسريع تطوير الواجهة وتحسين مظهرها، سنستخدم مكتبة Semantic UI React. يمكنك زيارة الموقع الرسمي عبر الرابط التالي:
https://react.semantic-ui.com/
بعد الدخول إلى الموقع، انتقل إلى قسم Get Started لبدء التثبيت.

تثبيت 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 مع محاذاة العنوان في المنتصف وإضافة مسافة داخلية مناسبة.

إضافة عناصر الإدخال والترجمة
نحتاج الآن إلى أربعة عناصر رئيسية داخل الواجهة:
- حقل لإدخال النص المراد ترجمته.
- قائمة منسدلة لاختيار اللغة.
- حقل لعرض الترجمة الناتجة.
- زر لتنفيذ الترجمة.
استورد المكوّنات اللازمة من 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، وهي خدمة مناسبة لترجمة النصوص واكتشاف اللغة المصدر وجلب اللغات المدعومة.

سنستخدم ثلاث نقاط نهاية رئيسية:
- /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 هنا مرة واحدة عند تحميل المكوّن، لأن مصفوفة الاعتماد فارغة.

تخزين قائمة اللغات في 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>

التقاط رمز اللغة المختارة
بعد عرض اللغات، نحتاج إلى معرفة اللغة التي يختارها المستخدم حتى نستخدم رمزها في طلب الترجمة.
إنشاء 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

تنفيذ 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 كلما تغيّر النص المدخل، وبالتالي سيتم تحديث لغة المصدر تلقائياً.

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