۱. مقدمه: چرا i18n Automation از Regex ساده فراتر میرود
اگر شما هم مثل من ساعتها وقت صرف پیدا کردن رشتههای هاردکد در یک پروژه لجسی کرده باشید، میدانید که بحث i18n فقط «پیدا کردن رشتهها» نیست. مسئله اصلی، درک Context است: این رشته در چه فریمورکی نوشته شده؟ باید به چه فرمتی تبدیل شود؟ کلید مناسب برای آن چیست؟ و چطور باید دوباره در کد تزریق شود؟
وقتی ساخت LocEngine را شروع کردم، خیلیها گفتند: «همین که با Regex رشتهها رو پیدا کنی کافیه». اما بعد از تحلیل عمیق نیازمندیها، فهمیدم که حداقل ۵ سناریوی کاملاً متفاوت برای اسکن وجود دارد و بحث تزریق کلیدها، دنیایی پیچیدهتر از اسکن است.
«پیدا کردن رشتههای هاردکد فقط ۲۰٪ مسئله است. ۸۰٪ مابقی، ساختن کلیدهای معنادار، انتخاب فرمت صحیح، و تزریق امن در کد بدون شکستن ساختار آن است.»
۲. مسئله اصلی: تنوع فریمورکها و فرمتهای کلید i18n
تصور کنید پروژهای دارید که بکاند آن با Laravel نوشته شده و فرانتاند آن با Vue.js. در چنین پروژهای، شما با دو سیستم i18n کاملاً متفاوت روبرو هستید:
| فریمورک | نحوه استفاده از ترجمه | فرمت کلید | محل ذخیرهسازی |
|---|---|---|---|
| Laravel | {{ __('messages.welcome') }} |
کلیدهای dot-notation | فایلهای PHP یا JSON |
| Vue.js | {{ $t('welcome') }} |
کلیدهای ساده یا nested | فایل JSON |
| WordPress | |
رشته اصلی + textdomain | PO/POT files |
| Python/Django | _('Welcome') |
رشته اصلی (gettext) | PO files |
یک ابزار ساده Regex محور، شاید بتواند رشته 'Welcome' را پیدا کند.
اما از کجا باید بفهمد که این رشته در فایل .blade.php باید تبدیل به
__('messages.welcome') شود، ولی در فایل .vue باید
$t('welcome') شود؟ اینجاست که Architecture وارد میدان میشود.
۳. معماری ۵ اسکنر تخصصی
در LocEngine 4.6.5، ما ۵ اسکنر مجزا طراحی کردهایم. هر کدام برای یک family از فریمورکها بهینه شده است. این تفکیک عمدی است - یک اسکنر واحد نمیتواند هم برای PyQt مناسب باشد و هم برای React:
🖥️ GUI Scan - برای رابطهای کاربری پایتون
این اسکنر تخصصی، فریمورکهای Tkinter، PyQt، Kivy، wxPython و CustomTkinter را پوشش میدهد. تفاوت اصلی آن با بقیه اسکنرها در این است که به دنبال widget properties میگردد:
# قبل از اسکن (Tkinter)
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text="Welcome to our app")
button = tk.Button(root, text="Click Me")
# بعد از تزریق خودکار
import tkinter as tk
from i18n import _
root = tk.Tk()
label = tk.Label(root, text=_("Welcome to our app"))
button = tk.Button(root, text=_("Click Me"))
🔄 Fallback Scan - برای وب و CLI پایتون
پروژههای Django، Flask، FastAPI و حتی ابزارهای CLI مثل Click و argparse الگوهای متفاوتی دارند. Fallback Scanner میتواند تشخیص دهد که یک رشته داخل یک template جنگو است یا یک command-line helper text.
🔍 Full Scan - قدرت ترکیبی برای PHP و Python
این اسکنر برای پروژههای Full-Stack طراحی شده. وقتی Laravel با Vue.js ترکیب میشود، Full Scanner هر دو بخش را همزمان اسکن میکند و مهمتر از آن، میفهمد مرز بین فریمورکها کجاست.
🌐 Web Scan - فرانتاند مدرن
React (JSX/TSX)، Vue (SFC)، Angular (templates)، HTML5 و CSS. این اسکنر context display دارد که در سه حالت Single Line، Extended (±۱۰ خط) و Full Function/Block کار میکند.
🔤 i18n Scan - برای پروژههای i18n شده
برخلاف بقیه اسکنرها، این یکی به دنبال رشتههای هاردکد نیست.
بلکه کلیدهای ترجمه موجود را از ۱۲+ سیستم مختلف استخراج میکند
(WordPress __(), React t(), Vue $t(), Python _() و غیره)
و میتواند با فایلهای ترجمه فعلی Smart Merge انجام دهد.
۴. موتور تزریق هوشمند: قلب LocEngine
اسکن کردن فقط نیمه اول ماجراست. نیمه دوم - و شاید مهمتر - تزریق کلیدهای i18n به جای رشتههای هاردکد است. این کار باید:
- کلید مناسب با ساختار همان فریمورک بسازد
- در محل دقیق تزریق شود بدون آسیب به syntax
- Import statementهای لازم را اضافه کند
- فایلهای ترجمه را با فرمت صحیح تولید کند
الگوریتم تولید کلید هوشمند
یکی از پیچیدهترین بخشهای LocEngine، Key Generator است. نمیتوان هر رشتهای را با یک کلید تصادفی جایگزین کرد. کلید باید معنادار باشد. الگوریتم ما به این صورت کار میکند:
# الگوریتم سادهشده Key Generator
def generate_key(string, framework_context):
# ۱. تحلیل معنایی رشته
tokens = tokenize_and_clean(string)
# ۲. شناسایی context از روی فریمورک و محل فایل
context = detect_context(filepath, framework)
# ۳. ساخت کلید بر اساس فریمورک
if framework == 'laravel':
# Laravel از dot-notation استفاده میکند
prefix = context.get_prefix() # مثلاً 'messages' یا 'validation'
key = f"{prefix}.{snake_case(tokens)}"
elif framework == 'vue':
# Vue معمولاً از کلیدهای camelCase یا nested استفاده میکند
key = camelCase(tokens)
elif framework == 'wordpress':
# WordPress از خود رشته اصلی به عنوان کلید استفاده میکند
key = string # اما textdomain اضافه میشود
elif framework == 'python':
# Python/gettext هم از خود رشته استفاده میکند
key = string
# ۴. بررسی یکتایی کلید
if key_exists(key):
key = append_context_to_key(key, context)
return key
resources/views/auth/login.blade.php باشد،
پیشوند auth. به کلید اضافه میشود.
در Vue، اگر داخل کامپوننت UserProfile.vue باشد،
کلید به صورت userProfile.welcomeMessage تولید میشود.
فرآیند تزریق: جایگزینی امن
تزریق کلید جدید به جای رشته قدیمی، باید بدون شکستن ساختار کد انجام شود. این کار با استفاده از AST (Abstract Syntax Tree) انجام میشود، نه String Replacement ساده:
# روش نادرست (String Replace - خطرناک)
کد_جدید = کد_اصلی.replace('"Welcome"', '__("messages.welcome")')
# ⚠️ مشکل: اگر "Welcome" در جای دیگری هم باشد، اشتباه جایگزین میشود
# روش صحیح (AST-based Injection)
import ast
tree = ast.parse(کد_اصلی)
# ۱. درخت syntax را parse کن
# ۲. nodeهای مربوطه را پیدا کن
# ۳. فقط همان node خاص را تغییر بده
# ۴. tree را دوباره به کد تبدیل کن
modified_code = ast.unparse(tree)
# ✅ فقط رشتههای قابل ترجمه تغییر میکنند
۵. پروژههای چندفریمورکی: چالش واقعی
جایی که LocEngine واقعاً میدرخشد، پروژههای چندفریمورکی است. تصور کنید یک پروژه با این ساختار:
my-fullstack-app/
├── backend/ # Laravel
│ ├── app/
│ │ └── Http/
│ │ └── Controllers/
│ │ └── ProductController.php
│ └── resources/
│ └── views/
│ └── products/
│ └── index.blade.php
├── frontend/ # Vue.js
│ └── src/
│ ├── components/
│ │ └── ProductCard.vue
│ └── pages/
│ └── Products.vue
└── admin/ # Python/FastAPI
└── templates/
└── dashboard.html
یک ابزار معمولی باید سه بار اجرا شود: یک بار برای Laravel، یک بار برای Vue، یک بار برای Python. و بدتر از آن، هیچ هماهنگی بین کلیدهای تولید شده وجود ندارد.
اما LocEngine با حالت Full Scan:
- هر بخش را با اسکنر مناسب خودش تحلیل میکند
- کلیدها را با پیشوندهای context-aware تولید میکند
- فایلهای ترجمه مجزا با فرمت صحیح برای هر فریمورک میسازد
- همپوشانیها را تشخیص میدهد - اگر یک عبارت در هر دو بخش استفاده شده باشد
# نمونه خروجی برای پروژه بالا:
backend/lang/en/messages.php:
'products.title' => 'Our Products',
'products.add_to_cart' => 'Add to Cart',
frontend/src/locales/en.json:
"productCard": {
"addToCart": "Add to Cart", # ← تشخیص همپوشانی!
"outOfStock": "Out of Stock"
}
۶. یک مثال واقعی: اسکن و تزریق در Laravel + Vue
قبل از LocEngine:
// File: backend/resources/views/products/index.blade.php
<div class="product-card">
<h2>Our Products</h2>
<p>Showing all available products in our store</p>
<button>Add to Cart</button>
<span>Out of Stock</span>
</div>
// File: frontend/src/components/ProductCard.vue
<template>
<div class="product-card">
<h2>{{ title }}</h2>
<button @click="addToCart">Add to Cart</button>
<span v-if="!inStock">Out of Stock</span>
</div>
</template>
بعد از LocEngine (Full Scan + Smart Injection):
// File: backend/resources/views/products/index.blade.php
<div class="product-card">
<h2>{{ __('products.title') }}</h2>
<p>{{ __('products.all_available') }}</p>
<button>{{ __('products.add_to_cart') }}</button>
<span>{{ __('products.out_of_stock') }}</span>
</div>
// File: frontend/src/components/ProductCard.vue
<template>
<div class="product-card">
<h2>{{ title }}</h2>
<button @click="addToCart">{{ $t('productCard.addToCart') }}</button>
<span v-if="!inStock">{{ $t('productCard.outOfStock') }}</span>
</div>
</template>
<script>
import { useI18n } from 'vue-i18n'
const { t } = useI18n() // ← به صورت خودکار اضافه شده
</script>
۷. درسهایی که در مسیر ساخت LocEngine آموختم
۷.۱. Regex کافی نیست - به AST نیاز دارید
بزرگترین اشتباه اولیه من: فکر میکردم با چند Regex خوب میشود همه چیز را حل کرد. اما وقتی به template strings در Vue یا JSX expressions در React رسیدم، فهمیدم که بدون درک ساختار درختی کد، نمیشود کار درستی انجام داد.
۷.۲. هر فریمورک یک «شخصیت» دارد
WordPress از توابع __() و _e() استفاده میکند.
Laravel از __() با dot-notation.
React از t() یا <Trans>.
نمیشود یک راهحل واحد برای همه ساخت.
باید برای هر کدام استراتژی جداگانه داشت.
۷.۳. تزریق، خطرناکترین بخش است
پیدا کردن رشتهها آسان است. جایگزینی امن آنها بدون breaking changes چالش اصلی است. یک اشتباه کوچک میتواند کل پروژه را از کار بیندازد. به همین دلیل LocEngine:
- قبل از هر تغییری، backup کامل میگیرد
- قابلیت Dry Run دارد (نمایش تغییرات بدون اعمال)
- سیستم Rollback برای بازگشت به حالت قبل
۷.۴. اعتماد کاربر مهمترین ویژگی است
هیچ توسعهدهندهای حاضر نیست ابزاری را روی کد production اجرا کند که به آن اعتماد نداشته باشد. به همین دلیل:
- شفافیت کامل: نمایش دقیق هر تغییری که قرار است اعمال شود
- Context Display: کاربر باید ببیند رشته در چه بافتی استفاده شده
- Selective Injection: کاربر میتواند انتخاب کند کدام رشتهها را تبدیل کند
۸. جمعبندی: فراتر از ابزار، یک طرز فکر
LocEngine را نساختم که فقط «یک ابزار i18n» باشد. ساختمش چون باور دارم توسعهدهندهها نباید وقتشان را صرف کارهای تکراری کنند. وقتی یک پروژه لجسی را از ۴۰ ساعت کار دستی به ۱۵ دقیقه کار خودکار میرسانید، یعنی دارید زمان را برای خلاقیت آزاد میکنید.
معماری ۵ اسکنر و موتور تزریق هوشمند، نتیجه ماهها آزمون و خطا، مطالعه عمیق فریمورکهای مختلف و مهمتر از همه، گوش دادن به نیازهای واقعی توسعهدهندگان است. هر قابلیت LocEngine - از Smart Context Detection تا Multi-Framework Injection - یک نفر جایی در دنیا داشته که به آن نیاز واقعی داشته.
«ابزارهای خوب از روی مستندات ساخته نمیشوند. آنها از دل دردهای واقعی توسعهدهندگان متولد میشوند. LocEngine هم از دل پروژههای خودم متولد شد - جایی که واقعاً به چنین ابزاری احتیاج داشتم و وجود نداشت.»
اگر پروژهای دارید که از i18n جا مانده، یا سیستمی که نیاز به مهاجرت به ساختار چندزبانه دارد، خوشحال میشوم با هم راهحل را بررسی کنیم. میتوانید از طریق LocEngine.com یا ایمیل مستقیم با من در تماس باشید.
💬 نظرات و پرسشها
تجربه شما با i18n Automation چطور بوده؟ آیا با چالشهای مشابهی روبرو شدهاید؟ نظرات و سوالات خود را با من به اشتراک بگذارید.