python developer tools automation, translation localization software, freelance developer coding

بناء أداة ذكية خاصة باحتياجاتك باستخدام بايثون

|

تعلّم كيف تبني أدواتٍ ذكيةً ببايثون مصمَّمةً لاحتياجاتك تحديداً — من محلل منافسين لمولد عروض أسعار، وصولاً لأدواتٍ متخصصةٍ للمترجمين والقائمين بالتوطين اللغوي.

عدد الكلمات: ~٢٠٠٠ · مدة القراءة: ١٠ دقائق

بناء أداةٍ ذكيةٍ خاصةٍ باحتياجاتك باستخدام بايثون

من محلل منافسين ومولد عروض أسعار، إلى أدواتٍ متخصصةٍ تحلّ مشاكل المترجمين والقائمين بالتوطين اللغوي


ملاحظة للقارئ: هذا المقال مستقلٌّ تماماً ويمكنك تطبيق كل ما فيه دون قراءة مقالاتٍ سابقة. لكن إذا أردت فهم كيف تُدمج الذكاء الاصطناعي مع أدواتك، فننصحك بمراجعة مقالتنا: كيف تستخدم نماذج الذكاء الاصطناعي داخل مشاريعك ببايثون.

في هذه السلسلة تعلّمنا كيف تُثبّت بايثون، وكيف تُؤتمت مهامك اليومية، وكيف تبني مواقع ويبٍ ديناميكية وواجهات برمجة تطبيقاتٍ قابلةً للبيع. لكن السؤال الذي يُبقيك مستيقظاً كفريلانسر ليس «ماذا أعرف عن بايثون؟» بل «ما المشكلة التي أعانيها كل يومٍ ولم يبنِ أحدٌ بعد أداةً لحلّها؟»

أثمن الأدوات ليست تلك التي تجدها جاهزةً على الإنترنت، بل تلك التي تبنيها أنت لنفسك ولعملائك لأنك تفهم المشكلة من الداخل. في هذا المقال من منصة ذي يزن، سنبني ثلاث أدواتٍ عملية: الأولى لأتمتة تحليل المنافسين، والثانية والثالثة مُصمَّمتان تحديداً لاحتياجات المترجمين والقائمين بالتوطين اللغوي — وهو مجالٌ يعاني نقصاً حقيقياً في الأدوات العربية المتخصصة.

python smart tools developer freelancer

الأداة الأولى: مولّد عروض أسعار ذكيٌّ للفريلانسر

من أكثر المهام المُضيِّعة للوقت في عمل الفريلانسر كتابة عروض الأسعار. كل مشروعٍ مختلفٌ، لكن البنية دائماً متكررة: وصف المشروع، المهام المفصَّلة، الجدول الزمني، السعر، شروط الدفع. لنبنِ أداةً تأخذ وصفاً موجزاً للمشروع وتُنتج عرض أسعار احترافياً بصيغة 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 منظَّمٍ يعرف فيه السياق الكامل لكل نصٍّ — من أي ملفٍ جاء وفي أي سطر — فيملأ عمود الترجمة العربية ويُعيد الملف للمطوّر الذي يُعيد حقن الترجمات في الكود تلقائياً.

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

الدرس الأهم: الأداة الأفضل هي التي تحلّ مشكلتك أنت

الأدوات الثلاث في هذا المقال لها قاسمٌ مشترك: كلٌّ منها وُلدت من ألمٍ حقيقيٍّ يعانيه محترفٌ في مجاله. مولّد عروض الأسعار من ضيق الفريلانسر من تكرار العمل الإداري. وفاحص التوطين من مترجمٍ اكتشف تشوّهات الواجهة بعد التسليم. ومستخرج النصوص من ساعاتٍ ضائعةٍ في فرز الكود يدوياً.

كل محترفٍ في مجاله يعرف عشر مشاكلٍ يدوية متكررة لم يبنِ أحدٌ بعد أداةً لحلّها، لأن لا أحد يعرفها جيداً بما يكفي غيره. لغة بايثون الذي تعلّمتها في هذه السلسلة تُتيح لك تحويل هذه المعرفة الخاصة إلى أداةٍ تبيعها لمئة محترفٍ آخر يعاني المشكلة ذاتها.

Python code displayed on screen in an exciting way


خلاصة المقال والخطوة القادمة

بنينا اليوم ثلاث أدواتٍ ذكيةٍ حقيقية: مولّد عروض أسعار بصيغة PDF يجمع الذكاء الاصطناعي مع ReportLab، وفاحص جودة توطينٍ يحمي المترجم من مفاجآت الواجهة قبل التسليم، ومستخرج نصوصٍ يُحوّل مشاريع الكود الكاملة إلى ملفات Excel جاهزةٍ للترجمة. كل أداةٍ منها قابلةٌ للبيع كخدمةٍ مستقلة أو كجزءٍ من حزمة خدماتك.

الخطوة التالية الموصى بها:

وصلنا إلى ختام السلسلة تقريباً. في المقال الأخير نستعرض أفضل ثماني مكتباتٍ في بايثون يجب على كل فريلانسرٍ معرفتها في ٢٠٢٦، ليس كنظريةٍ بل بالحالات العملية التي يحتاجها كل يوم.

تابع معنا المقال السادس عشر: أفضل 8 مكتباتٍ بايثون يجب على كل فريلانسرٍ معرفتها في ٢٠٢٦.


المراجع والمصادر:

  1. التوثيق الرسمي لمكتبة ReportLab لتوليد PDF: ReportLab Documentation
  2. دليل مكتبة BeautifulSoup لتحليل HTML: BeautifulSoup4 Documentation
  3. مواصفات ملفات PO للترجمة (GNU gettext): GNU gettext — PO File Format
  4. معايير توطين واجهات المستخدم — دليل W3C للتعددية اللغوية: W3C — Strings and Bidi

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *