بناء أداة ذكية خاصة باحتياجاتك باستخدام بايثون
تعلّم كيف تبني أدواتٍ ذكيةً ببايثون مصمَّمةً لاحتياجاتك تحديداً — من محلل منافسين لمولد عروض أسعار، وصولاً لأدواتٍ متخصصةٍ للمترجمين والقائمين بالتوطين اللغوي.
عدد الكلمات: ~٢٠٠٠ · مدة القراءة: ١٠ دقائق
بناء أداةٍ ذكيةٍ خاصةٍ باحتياجاتك باستخدام بايثون
من محلل منافسين ومولد عروض أسعار، إلى أدواتٍ متخصصةٍ تحلّ مشاكل المترجمين والقائمين بالتوطين اللغوي
ملاحظة للقارئ: هذا المقال مستقلٌّ تماماً ويمكنك تطبيق كل ما فيه دون قراءة مقالاتٍ سابقة. لكن إذا أردت فهم كيف تُدمج الذكاء الاصطناعي مع أدواتك، فننصحك بمراجعة مقالتنا: كيف تستخدم نماذج الذكاء الاصطناعي داخل مشاريعك ببايثون.
في هذه السلسلة تعلّمنا كيف تُثبّت بايثون، وكيف تُؤتمت مهامك اليومية، وكيف تبني مواقع ويبٍ ديناميكية وواجهات برمجة تطبيقاتٍ قابلةً للبيع. لكن السؤال الذي يُبقيك مستيقظاً كفريلانسر ليس «ماذا أعرف عن بايثون؟» بل «ما المشكلة التي أعانيها كل يومٍ ولم يبنِ أحدٌ بعد أداةً لحلّها؟»
أثمن الأدوات ليست تلك التي تجدها جاهزةً على الإنترنت، بل تلك التي تبنيها أنت لنفسك ولعملائك لأنك تفهم المشكلة من الداخل. في هذا المقال من منصة ذي يزن، سنبني ثلاث أدواتٍ عملية: الأولى لأتمتة تحليل المنافسين، والثانية والثالثة مُصمَّمتان تحديداً لاحتياجات المترجمين والقائمين بالتوطين اللغوي — وهو مجالٌ يعاني نقصاً حقيقياً في الأدوات العربية المتخصصة.
الأداة الأولى: مولّد عروض أسعار ذكيٌّ للفريلانسر
من أكثر المهام المُضيِّعة للوقت في عمل الفريلانسر كتابة عروض الأسعار. كل مشروعٍ مختلفٌ، لكن البنية دائماً متكررة: وصف المشروع، المهام المفصَّلة، الجدول الزمني، السعر، شروط الدفع. لنبنِ أداةً تأخذ وصفاً موجزاً للمشروع وتُنتج عرض أسعار احترافياً بصيغة PDF:
pip install openai reportlab
from openai import OpenAI
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from datetime import datetime, timedelta
import json, os
client_ai = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
def generate_proposal_content(
project_brief: str,
freelancer_name: str,
hourly_rate: float,
specialty: str
) -> dict:
"""يُحلّل الوصف المُدخَل ويُنتج محتوى عرض الأسعار بالذكاء الاصطناعي."""
prompt = f"""أنت فريلانسرٌ خبيرٌ في {specialty}.
وصف المشروع الذي طلبه العميل:
"{project_brief}"
سعرك بالساعة: ${hourly_rate}
أنشئ محتوى عرض أسعار احترافي بـ JSON:
{{
"project_title": "عنوانٌ واضحٌ للمشروع",
"executive_summary": "ملخصٌ تنفيذيٌّ في 3 جملٍ يُثبت فهمك للمشروع",
"deliverables": [
{{"task": "اسم المهمة", "hours": 5, "description": "وصفٌ موجز"}}
],
"timeline_days": 14,
"total_hours": 20,
"payment_terms": "50% مقدماً، 50% عند التسليم",
"validity_days": 7
}}
الساعات يجب أن تكون واقعيةً ومنطقية. لا تتجاوز 5 مهام."""
response = client_ai.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
response_format={"type": "json_object"}
)
return json.loads(response.choices[0].message.content)
def build_proposal_pdf(
content: dict,
freelancer_name: str,
hourly_rate: float,
client_name: str,
output_file: str = "proposal.pdf"
):
"""يبني ملف PDF لعرض الأسعار بتنسيقٍ احترافي."""
doc = SimpleDocTemplate(output_file, pagesize=A4,
rightMargin=40, leftMargin=40,
topMargin=40, bottomMargin=40)
styles = getSampleStyleSheet()
story = []
red = colors.HexColor("#c0392b")
navy = colors.HexColor("#1a2e5e")
light = colors.HexColor("#f9f9f9")
title_style = ParagraphStyle('Title', parent=styles['Heading1'],
fontSize=22, textColor=red, spaceAfter=6)
sub_style = ParagraphStyle('Sub', parent=styles['Normal'],
fontSize=11, textColor=navy, spaceAfter=14)
normal = styles['Normal']
# ترويسة
story.append(Paragraph("FREELANCE PROJECT PROPOSAL", title_style))
story.append(Paragraph(content["project_title"], sub_style))
story.append(Paragraph(
f"Prepared by: {freelancer_name} | For: {client_name} | "
f"Date: {datetime.now().strftime('%Y-%m-%d')} | "
f"Valid until: {(datetime.now() + timedelta(days=content['validity_days'])).strftime('%Y-%m-%d')}",
normal))
story.append(Spacer(1, 16))
# الملخص التنفيذي
story.append(Paragraph("Executive Summary", ParagraphStyle(
'H2', parent=styles['Heading2'], textColor=navy, spaceAfter=6)))
story.append(Paragraph(content["executive_summary"], normal))
story.append(Spacer(1, 12))
# جدول المهام والتكلفة
story.append(Paragraph("Scope of Work & Pricing", ParagraphStyle(
'H2', parent=styles['Heading2'], textColor=navy, spaceAfter=6)))
table_data = [["Task", "Description", "Hours", "Cost (USD)"]]
total_cost = 0
for item in content["deliverables"]:
cost = item["hours"] * hourly_rate
total_cost += cost
table_data.append([
item["task"],
item["description"],
str(item["hours"]),
f"${cost:.0f}"
])
table_data.append(["", "", "TOTAL", f"${total_cost:.0f}"])
tbl = Table(table_data, colWidths=[130, 230, 50, 80])
tbl.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), navy),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('BACKGROUND', (0, 1), (-1, -2), light),
('GRID', (0, 0), (-1, -2), 0.5, colors.HexColor("#dddddd")),
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
('TEXTCOLOR', (2, -1), (-1, -1), red),
('LINEABOVE', (0, -1), (-1, -1), 1.5, red),
('FONTSIZE', (0, 0), (-1, -1), 9),
('ALIGN', (2, 0), (-1, -1), 'CENTER'),
('BOTTOMPADDING', (0, 0), (-1, -1), 7),
]))
story.append(tbl)
story.append(Spacer(1, 14))
# الجدول الزمني وشروط الدفع
story.append(Paragraph(
f"Timeline: {content['timeline_days']} business days after project kickoff.", normal))
story.append(Paragraph(
f"Payment Terms: {content['payment_terms']}", normal))
story.append(Spacer(1, 20))
story.append(Paragraph(
"Thank you for considering my proposal. I look forward to working with you!",
ParagraphStyle('Footer', parent=normal, textColor=navy)))
doc.build(story)
print(f"✅ عرض الأسعار جاهز: {output_file} (${total_cost:.0f} إجمالاً)")
# تجربة الأداة
proposal = generate_proposal_content(
project_brief="موقع بورتفوليو لمصمم جرافيك، صفحةٌ رئيسية وصفحة أعمال وصفحة تواصل، تصميمٌ بسيطٌ وسريع التحميل.",
freelancer_name="Ahmad Al-Freelancer",
hourly_rate=40,
specialty="تطوير الويب وتصميم المواقع"
)
build_proposal_pdf(proposal, "Ahmad Al-Freelancer", 40, "Sara Al-Designer")
في دقيقتين تحصل على ملف PDF جاهزٍ للإرسال. بإمكانك تطوير الأداة لاحقاً بإضافة شعار موقعك وألوان هويتك البصرية.
الأداة الثانية: فاحص جودة التوطين الآلي لواجهات المستخدم
من يعمل في مجال التوطين اللغوي Localization يعرف الألم جيداً: تُسلّم ترجمتك للمطوّر، ثم تكتشف بعد أسبوعٍ أن نصوص الأزرار العربية تخرج عن حدود التصميم، أو أن الجمل الطويلة تُشوّه الواجهة بسبب اتجاه RTL. هذه المشكلة يمكن اكتشافها مُسبقاً — قبل الإرسال للمطوّر — بسكريبت بايثون بسيط.
المشكلة: ملفات الترجمة JSON أو PO تحتوي مئات النصوص. فحصها يدوياً مستحيل. والمطوّر لا يُخبرك بالحدود النصية القصوى لكل عنصرٍ في الواجهة.
pip install Pillow
import json
import re
from dataclasses import dataclass
from typing import Optional
from PIL import Image, ImageDraw, ImageFont
import os
# ---- تعريف قواعد الواجهة ----
# كل عنصرٍ في الواجهة له حدٌّ أقصى للأحرف وسياقٌ لمساعدة المترجم
UI_RULES = {
"btn_": {"max_chars": 18, "context": "زرٌّ في الواجهة"},
"nav_": {"max_chars": 14, "context": "عنصر قائمة تنقل"},
"label_": {"max_chars": 30, "context": "تسميةٌ توضيحية"},
"title_": {"max_chars": 55, "context": "عنوانٌ رئيسي"},
"tooltip_": {"max_chars": 80, "context": "نصٌّ تلميحي عند التحويم"},
"error_": {"max_chars": 120, "context": "رسالة خطأ"},
"placeholder_": {"max_chars": 35, "context": "نصٌّ تلميحيٌّ داخل حقل إدخال"},
}
@dataclass
class LocalizationIssue:
key: str
original: str
translation: str
char_count: int
max_allowed: int
ui_context: str
severity: str # critical / warning
def detect_ui_rule(key: str) -> Optional[dict]:
"""يُحدّد القاعدة المنطبقة على المفتاح بناءً على بادئته."""
key_lower = key.lower()
for prefix, rule in UI_RULES.items():
if key_lower.startswith(prefix):
return rule
return None
def check_rtl_issues(text: str) -> list[str]:
"""يكتشف مشاكل محتملةً في نصوص RTL العربية."""
issues = []
# أرقامٌ بدون مسافةٍ قد تُسبّب قلب الاتجاه
if re.search(r'\d{4,}', text):
issues.append("أرقامٌ طويلة قد تُسبّب اضطراباً في اتجاه RTL")
# وجود نصٍّ إنكليزي وعربي مُتداخل
has_arabic = bool(re.search(r'[\u0600-\u06FF]', text))
has_latin = bool(re.search(r'[a-zA-Z]{3,}', text))
if has_arabic and has_latin:
issues.append("نصٌّ ثنائي اللغة — تحقّق من ترتيب الاتجاه في الواجهة")
return issues
def analyze_localization_file(
source_file: str, # ملف JSON الإنكليزي الأصلي
target_file: str, # ملف JSON العربي المُترجَم
report_file: str = "localization_report.txt"
) -> list[LocalizationIssue]:
"""
يفحص ملفَّي الترجمة ويُنتج تقريراً بالمشاكل المحتملة.
"""
with open(source_file, encoding="utf-8") as f:
source = json.load(f)
with open(target_file, encoding="utf-8") as f:
target = json.load(f)
issues = []
warnings = []
ok_count = 0
for key, original in source.items():
translation = target.get(key, "")
# مفتاحٌ غير مُترجَم
if not translation:
issues.append(LocalizationIssue(
key=key, original=original, translation="[مفقود]",
char_count=0, max_allowed=0,
ui_context="غير معروف", severity="critical"
))
continue
rule = detect_ui_rule(key)
rtl_issues = check_rtl_issues(translation)
if rule:
char_count = len(translation)
if char_count > rule["max_chars"]:
severity = "critical" if char_count > rule["max_chars"] * 1.3 else "warning"
issues.append(LocalizationIssue(
key=key, original=original, translation=translation,
char_count=char_count, max_allowed=rule["max_chars"],
ui_context=rule["context"], severity=severity
))
elif rtl_issues:
for rtl_issue in rtl_issues:
warnings.append(f"[RTL] {key}: {rtl_issue}")
else:
ok_count += 1
else:
ok_count += 1
# كتابة التقرير
with open(report_file, "w", encoding="utf-8") as f:
f.write("=" * 60 + "\n")
f.write("تقرير فحص جودة التوطين — UI Localization QA Report\n")
f.write(f"التاريخ: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M')}\n")
f.write("=" * 60 + "\n\n")
criticals = [i for i in issues if i.severity == "critical"]
warns = [i for i in issues if i.severity == "warning"]
f.write(f"✅ مفاتيح سليمة: {ok_count}\n")
f.write(f"🔴 مشاكل حرجة: {len(criticals)}\n")
f.write(f"🟡 تحذيرات: {len(warns)}\n")
f.write(f"⚠️ مشاكل RTL: {len(warnings)}\n\n")
if criticals:
f.write("─── المشاكل الحرجة (يجب إصلاحها قبل التسليم) ───\n")
for i in criticals:
f.write(f"\n🔑 المفتاح: {i.key}\n")
f.write(f" السياق: {i.ui_context}\n")
f.write(f" الأصل: {i.original}\n")
f.write(f" الترجمة: {i.translation}\n")
f.write(f" الأحرف: {i.char_count} / {i.max_allowed} مسموح\n")
f.write(f" التجاوز: +{i.char_count - i.max_allowed} حرف\n")
if warnings:
f.write("\n─── تحذيرات RTL ───\n")
for w in warnings:
f.write(f" {w}\n")
print(f"📋 التقرير محفوظٌ في: {report_file}")
print(f" 🔴 حرجة: {len(criticals)} | 🟡 تحذيرات: {len(warns)} | ✅ سليمة: {ok_count}")
return issues
def generate_visual_preview(
translation: str,
element_type: str = "button",
output_file: str = "preview.png"
):
"""
يُولّد لقطةً بصريةً تُحاكي كيف سيظهر النص في عنصر الواجهة.
مفيدٌ لإرسالها للعميل مع التقرير.
"""
sizes = {"button": (200, 50), "label": (300, 40), "title": (400, 60)}
w, h = sizes.get(element_type, (250, 50))
img = Image.new("RGB", (w, h), color="#f0f0f0")
draw = ImageDraw.Draw(img)
# إطار العنصر
draw.rectangle([2, 2, w-3, h-3], outline="#c0392b", width=2)
# النص — محاذاةٌ يمينية لـ RTL
# ملاحظة: للخط العربي الصحيح استخدم arabic_reshaper + bidi
draw.text((w//2, h//2), translation, fill="#1a2e5e", anchor="mm")
# مؤشر الحدّ
max_chars = UI_RULES.get(f"{element_type}_", {}).get("max_chars", 20)
color = "#c0392b" if len(translation) > max_chars else "#27ae60"
draw.text((5, 5), f"{len(translation)}/{max_chars}", fill=color)
img.save(output_file)
print(f"🖼️ معاينةٌ بصرية محفوظة: {output_file}")
كيف تستخدم الأداة؟
ملف en.json الأصلي يبدو هكذا:
{
"btn_submit": "Submit",
"btn_cancel": "Cancel",
"nav_dashboard": "Dashboard",
"title_welcome": "Welcome to your account",
"error_not_found": "The page you requested could not be found."
}
وملف ar.json المُترجَم:
{
"btn_submit": "إرسال الطلب والتأكيد",
"btn_cancel": "إلغاء",
"nav_dashboard": "لوحة التحكم",
"title_welcome": "مرحباً بك في حسابك الشخصي على المنصة",
"error_not_found": "الصفحة التي طلبتها غير موجودة أو ربما تم نقلها."
}
issues = analyze_localization_file("en.json", "ar.json")
# النتيجة:
# 🔴 حرجة: 1 (btn_submit — 22 حرفاً بدلاً من 18)
# 🟡 تحذيرات: 1 (title_welcome — 34 حرفاً بدلاً من 55 — بخير)
# ✅ سليمة: 3
بدلاً من أن يُخبرك المطوّر بعد أسبوعٍ بأن زر «إرسال الطلب والتأكيد» يكسر تصميم الواجهة، تكتشف ذلك بنفسك خلال ثوانٍ قبل التسليم.
الأداة الثالثة: مستخرج سياق ومصطلحات الترجمة الذكي
المشكلة الثانية التي يعاني منها المترجمون يومياً: العميل يُرسل ملفاتٍ برمجيةً كاملةً (HTML أو JavaScript أو Python) ويطلب ترجمة النصوص الظاهرة للمستخدم. استخراج هذه النصوص يدوياً من بين آلاف أسطر الكود مهمةٌ مُضنيةٌ ومُعرَّضةٌ للخطأ. هذه الأداة تُنجزها في ثوانٍ.
pip install pandas beautifulsoup4 openpyxl
import re
import os
import pandas as pd
from bs4 import BeautifulSoup
from pathlib import Path
from dataclasses import dataclass, field
@dataclass
class ExtractedString:
file_name: str
line_number: int
context: str # السطر الكامل للسياق
source_text: str # النص الإنكليزي المُستخرَج
string_type: str # html_text / js_string / py_string / alt_text
translation: str = "" # حقلٌ فارغٌ للمترجم
def extract_from_html(file_path: str) -> list[ExtractedString]:
"""يستخرج النصوص الظاهرة للمستخدم من ملفات HTML."""
results = []
with open(file_path, encoding="utf-8") as f:
content = f.read()
lines = content.splitlines()
soup = BeautifulSoup(content, "html.parser")
# النصوص داخل العناصر — تجاهل السكريبت والستايل
skip_tags = {"script", "style", "code", "pre"}
for element in soup.find_all(string=True):
if element.parent.name in skip_tags:
continue
text = element.strip()
if len(text) < 2 or not re.search(r'[a-zA-Z]', text):
continue
if re.match(r'^[\d\s\W]+$', text):
continue
# البحث عن رقم السطر التقريبي
line_num = 0
for i, line in enumerate(lines, 1):
if text[:20] in line:
line_num = i
break
results.append(ExtractedString(
file_name=Path(file_path).name,
line_number=line_num,
context=lines[line_num-1].strip() if line_num else "",
source_text=text,
string_type="html_text"
))
# نصوص alt للصور — مهمةٌ لإمكانية الوصول Accessibility
for img in soup.find_all("img", alt=True):
alt = img.get("alt", "").strip()
if len(alt) > 1 and re.search(r'[a-zA-Z]', alt):
results.append(ExtractedString(
file_name=Path(file_path).name,
line_number=0,
context=str(img)[:80],
source_text=alt,
string_type="alt_text"
))
return results
def extract_from_javascript(file_path: str) -> list[ExtractedString]:
"""يستخرج النصوص النصية String Literals من ملفات JavaScript."""
results = []
with open(file_path, encoding="utf-8") as f:
lines = f.readlines()
# نمطٌ يستخرج النصوص الإنكليزية من علامات اقتباس
pattern = re.compile(r'''["'`]([A-Za-z][^"'`\n]{4,80})["'`]''')
# نصوصٌ تقنية يجب تجاهلها
skip_patterns = [
r'^https?://', r'^\./', r'\.(js|css|png|jpg|svg)$',
r'^[A-Z_]+$', # ثوابتٌ بالأحرف الكبيرة
r'^\s*//.*', # تعليقات
]
for line_num, line in enumerate(lines, 1):
# تجاهل أسطر التعليقات
stripped = line.strip()
if stripped.startswith("//") or stripped.startswith("*"):
continue
for match in pattern.finditer(line):
text = match.group(1).strip()
if any(re.search(p, text) for p in skip_patterns):
continue
# التأكد من وجود كلماتٍ إنكليزية حقيقية
if len(text.split()) < 1:
continue
results.append(ExtractedString(
file_name=Path(file_path).name,
line_number=line_num,
context=line.strip()[:100],
source_text=text,
string_type="js_string"
))
return results
def extract_from_python(file_path: str) -> list[ExtractedString]:
"""يستخرج النصوص الظاهرة للمستخدم من ملفات Python (رسائل، عناوين، تسميات)."""
results = []
with open(file_path, encoding="utf-8") as f:
lines = f.readlines()
# نمطٌ يستهدف النصوص في سياقٍ واجهي
ui_patterns = [
re.compile(r'''(?:label|title|message|text|placeholder|description|error|success|warning|info)\s*[=:]\s*["']([A-Za-z][^"'\n]{3,100})["']''', re.I),
re.compile(r'''_\(["']([A-Za-z][^"'\n]{3,100})["']\)'''), # نمط i18n gettext
re.compile(r'''print\(["']([A-Za-z][^"'\n]{3,80})["']\)'''),
]
for line_num, line in enumerate(lines, 1):
if line.strip().startswith("#"):
continue
for pattern in ui_patterns:
for match in pattern.finditer(line):
text = match.group(1).strip()
if len(text) > 3:
results.append(ExtractedString(
file_name=Path(file_path).name,
line_number=line_num,
context=line.strip()[:100],
source_text=text,
string_type="py_string"
))
return results
def export_to_excel(
strings: list[ExtractedString],
output_file: str = "translation_strings.xlsx"
):
"""
يُصدّر النصوص المُستخرَجة إلى Excel جاهزٍ للمترجم.
كل صفٍّ يحتوي: الملف، رقم السطر، السياق، النص الأصلي، حقل الترجمة.
"""
# إزالة التكرار
seen = set()
unique = []
for s in strings:
if s.source_text not in seen:
seen.add(s.source_text)
unique.append(s)
df = pd.DataFrame([{
"الملف": s.file_name,
"رقم السطر": s.line_number,
"نوع العنصر": s.string_type,
"السياق (الكود)": s.context,
"النص الأصلي": s.source_text,
"الترجمة العربية": "",
"ملاحظات المترجم": "",
} for s in unique])
with pd.ExcelWriter(output_file, engine="openpyxl") as writer:
df.to_excel(writer, index=False, sheet_name="Strings for Translation")
ws = writer.sheets["Strings for Translation"]
# تنسيق العمود الرئيسي
from openpyxl.styles import PatternFill, Font, Alignment
header_fill = PatternFill("solid", fgColor="1A2E5E")
trans_fill = PatternFill("solid", fgColor="FFF8F8")
for cell in ws[1]:
cell.fill = header_fill
cell.font = Font(color="FFFFFF", bold=True)
cell.alignment = Alignment(horizontal="center")
# تمييز عمود الترجمة بلونٍ وردي
trans_col = 6
for row in ws.iter_rows(min_row=2, min_col=trans_col, max_col=trans_col):
for cell in row:
cell.fill = trans_fill
cell.alignment = Alignment(horizontal="right")
# تعديل عرض الأعمدة
column_widths = [20, 12, 15, 50, 40, 40, 25]
for i, width in enumerate(column_widths, 1):
ws.column_dimensions[chr(64 + i)].width = width
print(f"✅ ملف الترجمة جاهز: {output_file}")
print(f" {len(unique)} نصٍّ فريدٍ بعد إزالة التكرار من {len(strings)} نتيجة")
def process_project_folder(folder_path: str, output_file: str = "translation_strings.xlsx"):
"""
يعالج مجلداً كاملاً يحتوي ملفاتٍ متعددة الأنواع دفعةً واحدة.
"""
all_strings = []
folder = Path(folder_path)
for file_path in folder.rglob("*"):
if file_path.suffix == ".html":
all_strings.extend(extract_from_html(str(file_path)))
elif file_path.suffix == ".js":
all_strings.extend(extract_from_javascript(str(file_path)))
elif file_path.suffix == ".py":
all_strings.extend(extract_from_python(str(file_path)))
print(f"🔍 فُحص {folder_path}: وُجد {len(all_strings)} نصٍّ قابلٍ للترجمة")
export_to_excel(all_strings, output_file)
# تجربة الأداة على مجلد مشروع
process_project_folder("./my_project/", "translation_strings.xlsx")
يحصل المترجم على ملف إكسل Excel منظَّمٍ يعرف فيه السياق الكامل لكل نصٍّ — من أي ملفٍ جاء وفي أي سطر — فيملأ عمود الترجمة العربية ويُعيد الملف للمطوّر الذي يُعيد حقن الترجمات في الكود تلقائياً.
ما كان يستغرق ساعاتٍ من الفرز اليدوي في ملفاتٍ مئات الأسطر، يُنجزه هذا السكريبت في ثوانٍ مع ضمان عدم إغفال أي نصٍّ أو تخريب الكود البرمجي.
الدرس الأهم: الأداة الأفضل هي التي تحلّ مشكلتك أنت
الأدوات الثلاث في هذا المقال لها قاسمٌ مشترك: كلٌّ منها وُلدت من ألمٍ حقيقيٍّ يعانيه محترفٌ في مجاله. مولّد عروض الأسعار من ضيق الفريلانسر من تكرار العمل الإداري. وفاحص التوطين من مترجمٍ اكتشف تشوّهات الواجهة بعد التسليم. ومستخرج النصوص من ساعاتٍ ضائعةٍ في فرز الكود يدوياً.
كل محترفٍ في مجاله يعرف عشر مشاكلٍ يدوية متكررة لم يبنِ أحدٌ بعد أداةً لحلّها، لأن لا أحد يعرفها جيداً بما يكفي غيره. لغة بايثون الذي تعلّمتها في هذه السلسلة تُتيح لك تحويل هذه المعرفة الخاصة إلى أداةٍ تبيعها لمئة محترفٍ آخر يعاني المشكلة ذاتها.
خلاصة المقال والخطوة القادمة
بنينا اليوم ثلاث أدواتٍ ذكيةٍ حقيقية: مولّد عروض أسعار بصيغة PDF يجمع الذكاء الاصطناعي مع ReportLab، وفاحص جودة توطينٍ يحمي المترجم من مفاجآت الواجهة قبل التسليم، ومستخرج نصوصٍ يُحوّل مشاريع الكود الكاملة إلى ملفات Excel جاهزةٍ للترجمة. كل أداةٍ منها قابلةٌ للبيع كخدمةٍ مستقلة أو كجزءٍ من حزمة خدماتك.
الخطوة التالية الموصى بها:
وصلنا إلى ختام السلسلة تقريباً. في المقال الأخير نستعرض أفضل ثماني مكتباتٍ في بايثون يجب على كل فريلانسرٍ معرفتها في ٢٠٢٦، ليس كنظريةٍ بل بالحالات العملية التي يحتاجها كل يوم.
تابع معنا المقال السادس عشر: أفضل 8 مكتباتٍ بايثون يجب على كل فريلانسرٍ معرفتها في ٢٠٢٦.
المراجع والمصادر:
- التوثيق الرسمي لمكتبة ReportLab لتوليد PDF: ReportLab Documentation
- دليل مكتبة BeautifulSoup لتحليل HTML: BeautifulSoup4 Documentation
- مواصفات ملفات PO للترجمة (GNU gettext): GNU gettext — PO File Format
- معايير توطين واجهات المستخدم — دليل W3C للتعددية اللغوية: W3C — Strings and Bidi


