PDA

View Full Version : دو زبانه کردن برنامه



eshpilen
چهارشنبه 16 اسفند 1391, 22:20 عصر
دارم پروژهء سیستم رجیستر و لاگین خودم رو دو زبانه میکنم؛ یعنی زبان فارسی رو هم بهش اضافه میکنم. تاحالا زبان انگلیسی بود فقط.

خب اول رفتم سراغ راست به چپ کردن Layout صفحات.
خوشبختانه از چیزی که فکر میکردم ساده تر بود؛ البته شاید به لطف ساپورت مناسب مرورگرهای جدید (چون فکر کنم چند سال پیش دردسرش بیشتر بود).

برای راست به چپ کردن چیدمان صفحات اینطور عمل کردم:

در فایل common.php که در ابتدای تمام صفحات اینکلود میشه این کد رو گذاشتم:


if($lang=='fa') {
$page_dir='dir="rtl"';
$cell_align='align="left"';
}
else {
$page_dir='';
$cell_align='align="right"';
}

بعد در تگ html و تگ body صفحات به این شکل عمل کردم:


<html <?php echo $page_dir; ?>>

<body <?php echo $page_dir; ?>>

از متغییر cell_align هم برای چیدمان دقیق فیلدهای فرم ها (دقیقتر بگم، برچسب های اونها) استفاده میکنم؛ مثلا:


<td <?php echo $cell_align; ?>>Password:</td>

حالا تا اینجا که مشکل خاصی نبوده.
میخواستم ببینم چیز دیگه ای لازم نیست؟ و اینکه شما خودتون از چه روشی استفاده میکنید؟ این روش بنظرتون قابل قبوله؟

مورد مهم بعدی اینه که چطور متن ها رو در کدهای HTML علامتگذاری/جایگزین کنم.
مثلا:

<title>Login</title>
خودم تا اینجا توی فکر استفاده از یک تابع مترجم بودم:

<title><?php tr('Login'); ?></title>

این تابع tr میاد و معادل رشتهء ورودی رو در زبان جاری پیدا و echo میکنه.
البته فکر کنم لازم باشه یه پارامتر دوم آپشنال هم براش بذارم که اگر خواستم echo نکنه و فقط return کنه که بتونم مثلا بریزم توی متغییر دیگری یا با رشته ها concat کنم و این حرفا. اینکه حالت پیشفرضش چی باشه البته بستگی داره ببینم استفاده از کدوم حالت در کد بیشتره.

من فکر میکنم استفاده از یک تابع هم تمیز و خوانا و کوتاهه و هم انعطاف خوبی داره که بخوایم داخل تابع هر عملیاتی انجام بدیم و بعدا تغییراتی بدیم و چیزی اضافه و کم کنیم خیلی راحته.

حالا بحث چگونگی ذخیره سازی ترجمه ها رو ندارم فعلا چون پیشتر درموردش بحثهای زیاده شده بود و منم خوندم و دیدم که از روشهای مختلفی استفاده میشه و بنظرم در این زمینه محدودیت و مته به خشخاش گذاشتن درست نیست. یعنی روشهای مختلفی هست که همه، حداقل در شرایط معمولی، قابل قبول هستن؛ در این مورد زیاد نمیشه گفت مثلا فقط فلان روشها اصولیه یا فلان روش خیلی بهتره. بنظرم راحتی و سرعت برنامه نویسی و دیباگ و تغییر و توسعه مهمتر باشه (البته تا وقتی عملا مشکل فنی خاصی در یک روش پیدا نشده).
فعلا فقط اینکه در سطح کد از چه روشی استفاده کنم.
البته اگر خواستید و تجربه یا ایدهء خوبی دارید میتونید درمورد روش ذخیره سازی ترجمه ها هم صحبت کنید. مثلا ذخیرهء متن های معادل در فایل یا استفاده از define و غیره.

colors
پنج شنبه 17 اسفند 1391, 11:50 صبح
به نظرم نباید زیاد به اتریبیوت ها اطمینان کردو کلا زیادهم جالب نیست. همون متغیر lang از اول بگیر و بر اساس اون فایلهای مورد نیازتو لود کن. مثلا بهتره دو نوع CSS داشته باشی. یکی برای RTL و اون یکیشم LTR. کلا همین دوتا میشه دیگه. یعنی فقط راست به چپو، چپ به راست داریم که واسه همه زبانهای صدق میکنه. اینجوری انعطاف و کنترل بیشتری رو ظاهر برنامه ت داری.
حتی من خودم یه جای اینجوری استفاده کردم: کلا یه فایل استایل CSS داشتم و اینجوری کار کردم.

<?php
$lang = $_GET['lang'];
header("Content-Type: text/css");
?>
body{
background:red;
direction: <?php if($lang == 'fa' || $lang == 'ar'){echo 'rtl';} else{echo 'ltr';}?>
}

واسه نمایش متون هم یه همچین چیزی بهتره. مثلا: یه آرایه میسازی که برای تمام زبانها یکی هست. بعد مثلا اگه بگیم فقط 100 تا از متن یا کلمات سایت قابل تغییره، 100 تا عنصر تو آرایه تعریف میکنی، که نسبت به راحتی خودت میایو برای تمام زابنها شاخصهای آرایه برابری میزاری. مثلا برای کلمه "خروج" تو تموم زبانها ( فارسی، انگلیسی، اسپانیولی و ...) باید یه عنصر ثابت داشته باشی که شاخص اون عنصر هم ثابت باشه. مثلا:
فارسی:

<?php
# PERSIAN
$lang = array(
'exit' => 'خروج',
'hello' => 'سلام'
);
?>
انگلیسی:

<?php
# ENGLISH
$lang = array(
'exit' => 'Exit',
'hello' => 'Hello'
);
?>
اسپانیول:

<?php
# SPANISH
$lang = array(
'exit' => 'Salida',
'hello' => '¡Hola'
);
?>
هر کدام رو با نام زبان خودشون تو فایلهای جداگانه میزاری.
بعد مث همون CSS بر اساس پارامتر موجود در متغیر lang یکی از زبانهارو require میکنی.

<?php
$lang = $_GET['lang'];
if($lang == 'fa'){
require('lang/fa.php');
}
elseif($lang == 'en'){
require('lang/en.php');
}
elseif($lang == 'sp'){
require('lang/en.php');
}
else{
require('lang/fa.php'); # Defualt
}
?>

<html>
<body>
<P><?php echo $lang['hello']; ?></P>
</body>
</html>

Unique
پنج شنبه 17 اسفند 1391, 12:01 عصر
در مورد layout به نظرم روش شما اصلا اصولی نیست ، استفاده از php برای بحث های مرتبط با rtl و ltr و کلا align ها و غیره کار شما را الکی زیاد میکنه و اسپاگتی کدتون خیلی زیاد میشه ! اما راه حل. راه حل اینه که ما دو تا css برای layout داشته باشیم ، یکی rtl و یکی ltr ، اینطوری میتونیم فونتها مختلف ، style ها مختلف استفاده کنیم و راحت کل style و theme را عوض کنیم ، البته هر چی layout شما پیچیده تر باشه باید تسلط به css هم بیشتر باشه.

در مورد ترجمه هم من همچنان از define استفاده میکنم و فکر کنم سریع تر هم باشه (البته مطمئن نیستم چون تحقیق نکردم). اما تابع هم بد فکری نیست و به قول شما راه حل ها زیاده.

موفق باشین.

eshpilen
پنج شنبه 17 اسفند 1391, 20:50 عصر
ترجمه ها رو توی فایل ذخیره کردم.
حالا با یه مسئله ای مواجه شدم و اونم پیامهایی که توشون متغییر درج شده.
مثلا:

Only $account_block_threshold incorrect login attempts are permitted in every $account_block_period_msg.<br>Number of incorrect login attempts in the past $account_block_period_msg: $incorrect_attempts<br>Number of tries left: $tmp20
حالا من اینو مثلا توی فایل ترجمهء انگلیسی درج میکنم که بعد توسط تابع tr خونده و پس داده میشه؛ منتها مقدار متغییرها رو چطور باید در این رشته درج کرد.
من از این روش استفاده کردم که در فایل ترجمه بعنوان ترجمهء این مورد این عبارت رو وارد کردم:

echo "Only $account_block_threshold incorrect login attempts are permitted in every $account_block_period_msg.<br>Number of incorrect login attempts in the past $account_block_period_msg: $incorrect_attempts<br>Number of tries left: $tmp20";
بعد با تابع tr که این ترجمه رو میخونم پاس میدم به تابع eval که در نتیجه دستورات موجود در ترجمه رو اجرا میکنه و متغییرها هم جایگزین میشن:

eval(tr('login limit warning', false))

بنظر منکه خوبه. راحتی و سرعت برنامه نویسی ;)
البته ادعا ندارم که راه اصولی ای هست. فعلا درحد پروژهء شخصی خودم و کار راه اندازیه دیگه.
مثلا اگر به کاربران اجازه بدیم فایل ترجمه درست کنن، اونوقت ممکنه کسی از این ویژگی برای نفوذ سوء استفاده کنه (دستورات خطرناکی رو در فایل ترجمه وارد کنه).

ولی سوال اصلی ای که داشتم اینه که تابع eval رو که بطور معمول روی سرورها نمیبندن؟ (بخاطر امنیت و اینا).
البته بعید میدونم. چون این تابع مهم و پرکاربردی هست و کاملا در محیط داخلی PHP هم کار میکنه (بطور مستقیم با سیستمهای هاست کاری نداره).

eshpilen
پنج شنبه 17 اسفند 1391, 22:45 عصر
نه اون روش eval جالب نبود.
این روش بهتره بنظرم:
رشتهء ترجمه رو اینطور ذخیره میکنم:

Only %d incorrect login attempts are permitted in every %s.<br>Number of incorrect login attempts in the past %s: %d<br>Number of tries left: %d
بعد با تابع sprintf متغییرها رو جاگذاری میکنیم:

sprintf(tr('login limit warning', false), $account_block_threshold, $account_block_period_msg, $account_block_period_msg, $incorrect_attempts, $tmp20)

Unique
جمعه 18 اسفند 1391, 01:25 صبح
مشکل sprintf اینه که اگه جای متغیر ها در رشته عوض بشوند باید کد را تغییر بدین ! به نظر من str_replace از همه بهتره !

eshpilen
جمعه 18 اسفند 1391, 12:08 عصر
دست آخر به این نتیجه رسیدم که ذخیره سازی ترجمه ها در فایل خام روش مناسبی نیست؛ بخاطر هزینهء Open کردنهای فایل و همچنین جستجوی مجدد فایل به ازای هرکدام از ترجمه های مورد نیاز.

الان از آرایه برای ذخیره سازی ترجمه ها استفاده کردم.
این کدها رو میذارم تا برای دیگران هم مفید و آموزنده باشه درصورت امکان. بالاخره آدم وقت و کمک دیگران رو میگیره باید سعی کنه حتی الامکان چیزی هم به جامعه پس بده :چشمک:

مثلا یک فایل ترجمه فارسی به اسم lang_fa.php دارم که محتویاتش به این شکله:


<?php
if(ini_get('register_globals')) exit("<center><h3>Error: Turn that damned register globals off!</h3></center>");
if(!isset($parent_page)) exit("<center><h3>Error: Direct access denied!</h3></center>");

return array(
'login' => 'ورود',
'username' => 'نام کاربری',
'password' => 'رمز عبور',
'remember me' => 'بخاطرسپاری لاگین برای جلسات آینده',
'block-bypass mode' => 'حالت دور زدن بلاک',
'tie login to ip option description' => 'منجر به افزایش امنیت میشود، اما اگر IP شما عوض شود از لاگین خارج میشوید',
'tie my login to my ip address' => 'لاگین مرا به IP من وابسته کن',
'forgot password/username' => 'فراموشی رمزعبور/نام کاربری',
'login limit warning' => 'فقط %d لاگین اشتباه در هر %s مجاز است.<br>تعداد لاگین های اشتباه در %s گذشته: %d<br>تعداد لاگین های مجاز باقیمانده: %d',
'block_bypass_mode_max_logins' => 'توجه: حداکثر تعداد لاگین ناموفق <span style="color: red">%d</span> بار میباشد.'
);

?>

بعد یک تابع مترجم دارم که هرجا میخوام متنی درج کنم ازش استفاده میکنم:


<?php
if(ini_get('register_globals')) exit("<center><h3>Error: Turn that damned register globals off!</h3></center>");
if(!isset($parent_page)) exit("<center><h3>Error: Direct access denied!</h3></center>");

function tr($str, $echo=true) {

global $lang;
global $index_dir;
global $parent_page;

static $translations;
static $eng_translations;

$orig_str=$str;
$str=strtolower($str);

if(!$translations) $translations=require $index_dir."include/lang/lang_{$lang}.php";

if(array_key_exists($str, $translations)) {
if($echo) echo $translations[$str];
else return $translations[$str];
return;
}
else if($lang!='en') {
if(!$eng_translations) $eng_translations=require $index_dir.'include/lang/lang_en.php';
if(array_key_exists($str, $eng_translations)) {
if($echo) echo $eng_translations[$str];
else return $eng_translations[$str];
return;
}
}

if($echo) echo $orig_str;
else return $orig_str;

}

?>

بعد مثلا در کدهام اینطور استفاده میکنم:


<?php tr('Username'); ?>

و در صورتیکه در رشتهء ترجمه متغییرهایی هم باشن که باید جایگزین بشن، مثل این عمل میکنم:


sprintf(tr('block_bypass_mode_max_logins', false), $block_bypass_max_incorrect_logins)

خب این تابع tr چطور عمل میکنه.
اول فایل ترجمهء زبان جاری رو اینکلود میکنه، فرضا زبان جاری fa است، بعد دنبال ترجمه ای برای آرگومان پاس شده (متغییر str) میگرده توی آرایهء ترجمه ها، اگر ترجمه ای موجود بود اون رو برگشت میده یا اکو میکنه، اگر نبود بعد میره فایل ترجمهء en رو هم میخونه و اگر ترجمه ای توش بود اون رو echo یا return میکنه (بسته به پارامتر دوم تابع)، اگر ترجمه ای در فایل en هم پیدا نشد اونوقت همون رشتهء پاس شده رو بعنوان ترجمه برگشت میده یا اکو میکنه.
اینطوری لازم نیست مثلا برای Username که خودش میتونه بعنوان متن انگلیسی هم استفاده بشه یک ترجمه هم در فایل انگلیسی بذاریم. میتونیم از Username که به تابع tr پاس میکنیم بعنوان کلید برای پیدا کرن ترجمهء فارسی و بعنوان متن اصلی در زبان انگلیسی استفاده کنیم.

نکتهء دیگه اینکه آرایه های ترجمه در همون تابع tr خونده و در متغییرهای استاتیک ذخیره میشن (که در هر بار اجرای تابع دوباره خونده نشن).
راستی از بابت درج ‎$lang در دستور اینکلود نترسید! چک امنیتیش جای دیگه انجام میشه (با رگولار اکسپرشن چک میکنم که فقط متشکل از دو حرف انگلیسی باشه، مثلا en یا fa). البته شاید از نظر امنیتی اصولی تر باشه که این چک رو توی خود تابع (هم) بذارم!؟

البته کدها رو صرفا برای روشن شدن الگوریتم گذاشتم؛ احتمالا نمایش کدها مقداری خراب شده و کار نمیکنه.

colors
جمعه 18 اسفند 1391, 13:26 عصر
بد نیست، ولی به نظرت لازمه این همه بپیچونی؟ همون عناصر آرایه رو چاپ کن راحت

eshpilen
جمعه 18 اسفند 1391, 14:07 عصر
بنظرم پیچیدگیش به نسبت انعطاف و راحتی های بعدیش مناسبه.
الان اینکه تابع هست راحتتره بنظر من. حساب کنی تایپ اون مدل که شما میگی در مجموع بیشتره. این تابع از نظر اختصار کد و خوانایی هم تمیزتر درمیاد، چون خیلی جاها داخل خودش اکو میکنه و دیگه نیازی نیست یک دستور echo اضافه بذارم.
بعد اینکه مجبور نیستم برای عناوین کوتاه یک معادل در فایل انگلیسی وارد کنم بنظرم خوبه. فقط ترجمهء فارسیش رو درج میکنم در فایل فارسی.
چیز آنچنان پیچیده ای هم نداره واقعا. فقط یک تابع حدود 20 خطی هست دیگه!

البته این کار اوله، شاید بعدا باز تغییرش دادم.
مثلا تغییر از ذخیره سازی در فایل دیتا به ذخیره سازی در آرایه تغییر مفیدی بود که پیچیدگی و حجم کد رو کاهش داد.

eshpilen
شنبه 26 اسفند 1391, 00:17 صبح
وای این ترجمه ها از چیزی که فکر میکردم هم بیشتر کار میبرد.
واقعا عجب خرحمالی ای داره!
چند روز وقت بیکاریم رو صرفش کردم. منجمله تمام امروز که جمعه است.
دیگه آخراشه.

هر ترجمه که درست میکردم اون قسمت رو تست هم میکردم که بدون اشکال باشه؛ زیاد اشکال درمیامد؛ نمیشه فقط همینطور تایپ کرد و گذاشت رفت؛ در تست کلی آخر هم آخه آدم ممکنه بعضی موارد رو یادش بره تست کنه یا بهرصورت اون موقع ممکنه اصلا وقت بیشتری هم ببره.

میگم شما سفارشهایی که انجام میدید فقط به یک زبان مینویسید دیگه؟
بابت دو زبانه کردن چقدر طول میکشه چقدر میگیرید؟ مثلا بگی یک پروژه اگر کلش با یک زبان در 15 روز انجام میشه، بعد فرضا 3 روز هم صرف افزودن زبان دوم بشه خودش میشه 20% زمان بیشتر.
البته پروژهء من که خیلی زمان برد؛ چون گسترده و پیچیده است. ضمنا پیام هم زیاد داره بنظرم!!

تمام فایلها و تمام کدهای برنامه رو مرور کردم تا هرچی متن و پیام داره که نیاز به ترجمه دارن ترجمه کنم.
جاهایی که خودم هم فکر نمیکردم پیام داشتن که باید ترجمه میشدن.

حالا یه چیزی بنظرم رسید!
بنظرتون آدم این کارها رو باید همون موقع توسعه و کدنویسی اولیه انجام بده یا مثلا اول همهء متن ها و پیامها رو به انگلیسی یا فارسی (هرکدام که زبان اولیه/پیشفرض پروژه است) کار کنه و دست آخر که برنامه تموم شد بیاد و روی دو زبانه کردن و ترجمشون کار کنه؟

من فکر کنم اینکه موقع توسعه و تست اولیه روی ترجمه هم فکر و کار کنیم، باعث از دست رفتن تمرکز روی منطق و الگوریتم و امکانات اصلی برنامه میشه. تاحد امکان باید موقع تست و توسعه پارامترها رو کم کرد و بصورت مرحله به مرحله کارها رو تقسیم بندی کرد، نه اینکه همزمان مجبور باشی روی چند موضوع و جنبه کار و دقت کنی که بعضی از اونا واقعا کارهای جانبی و کم اهمیت تری هستن که میشد در زمان دیگری هم انجامشون داد.

راستی گفتم برنامه رو اول با زبان پیشفرض بنویسیم، ولی من فکر کنم ترجمهء انگلیسی به فارسی راحتتر از ترجمهء فارسی به انگلیسی باشه. چون توی کد فارسی که باشه خودش بهم ریختگی نمایشی و مشکل تر شدن اعمال تغییر در کد رو باعث میشه.

Unique
شنبه 26 اسفند 1391, 02:21 صبح
ترجمه با مشتری هست ! در حال حاضر پروژه ای به سه زبان فارسی و انگلیسی و عربی دارم ! کل فایل را میدم ترجمه کنند ! بدشم کافیه style را برای rtl یا ltr ست کنم و تمام !

eshpilen
شنبه 26 اسفند 1391, 08:48 صبح
کل فایل را میدم ترجمه کنند ! بدشم کافیه style را برای rtl یا ltr ست کنم و تمام !
کدوم فایل؟
یعنی از ابتدا تمام متن ها و پیامها رو توی فایل جداگانه ذخیره کردی؟
راستی قبلا هم این کار رو کردی؟ چون بنظرم به این سادگی هم نیستا.
مال من خیلی ترجمه هاش پارامتری شده، چون وسط متن مثلا مقدار یه متغییر باید درج بشه.
فرضا توی فایل ترجمه به این شکله:

'verification email sent msg' => 'یک ایمیل به <span style="white-space: pre; color: #080;">%s</span> ارسال شد',
البته این توش فرمت (HTML) هم داره.
و همین ترجمه در کد اینطور درج شده:

$success_msg='<h3>'.sprintf(tr('verification email sent msg'), htmlspecialchars($_POST['email'], ENT_QUOTES, 'UTF-8'));
اینم یه مدل دیگه که توش متغییر داره منتها این بار متغییر سمت کلاینت (جاوااسکریپت):

' is longer than "+max_length+" characters!' => ' بیشتر از "+max_length+" کاراکتر است!',
و در کد اینطور درج شده:

msgs[i++]=local_field_name+"<?php echo tr(' is longer than "+max_length+" characters!'); ?>";
تازه اون local_field_name خودش باز اسم فیلد به زبان local هست که جای دیگه ست شده.

eshpilen
شنبه 26 اسفند 1391, 09:04 صبح
راستی من دیگه یکسری پیامهای خطای داخلی برنامه رو ترجمه نکردم. چون در حالت عادی برای کاربر پیش نمیان و اگر هم پیش بیان کاربرد خاصی برای کاربر ندارن.
یکسری بخشهایی رو هم که مربوط به توسعه و تست بودن ترجمه نکردم؛ چون قرار نیست آدم عادی باهاشون کار کنه، بلکه برنامه نویس کار میکنه که خب برنامه نویس هم باید انگلیسی بلد باشه و کار کنه دیگه.

درسته؟

Unique
شنبه 26 اسفند 1391, 14:40 عصر
حق با شماست ، اما من از روش ما استفاده نمیکنم ،‌ببینید توی php دو تا تابع خوب هست یکی sprintf و یکی str_replace البته reg exp هم خوبه، من ترجمه ها را define میکنم توی یک فایل php یا میگذارم توی xml حالا مثل template engine ها عمل میکنم البته بستگی داره به خیلی مساپل sprintf محدودیت هایی ایجاد میکنه اما str_replace و reg exp اینطوری نیستند. مثلا بیا یک سیستم شبیه html طراحی کن ! مثلا برای وارد کردن نام توی یک رشته میشه از [NAME] استفاده کرد و بعد با str_replace جایگزین کرد ! اگه هم پیچیده تر بشه با reg exp. خلاصه من روش بهتر از این پیدا نکردم.

eshpilen
سه شنبه 29 اسفند 1391, 22:07 عصر
اومدم فقط ترجمه کنم، چندین باگ پیدا و برطرف کردم و یه چیزایی رو هم بهبود دادم و یه امکاناتی هم اضافه کردم.
البته خوشبختانه باگها اکثرا امنیتی نبودن.

البته در بخشهای مربوط به ادمین از نظر SQL Injection تنبلی و کم کاری کرده بودم با این فرض که ادمین مالک سایت هم هست و معنی نداره به سایتی که کامل زیر دست خودشه حمله کنه؛ ولی بعد فکر کردم این فرض لزوما درست نیست که ادمین برنامه ادمین خود هاست هم هست! مثلا ممکنه ما بعنوان مالک سایت برنامه رو نصب کنیم و بعد مشخصات ورود به اکانت ادمین رو بدیم به کس دیگه تا اون ادمین سیستم باشه.
خوشبختانه در بخشهای ادمین کار خیلی کمی نیاز بود تا در برابر SQL Injection امن بشه.

سیستم ضد CSRF رو هم یخورده قوی تر کردم؛ یک توکن رو دو توکن کردم که یکی برای درخواستهای GET و دیگری برای درخواستهای POST استفاده بشه. اینطور اگر توکن درخواستهای GET دست دیگران بیفته (چون دیتای موجود در Query string امنیت کمتری داره نسبت به دیتاهایی که در فیلدهای فرمها با درخواستهای POST منتقل میشن)، امنیت درخواستهای POST همچنان محفوظ میمونه. معمولا هم عملیات مهم که بخصوص در سمت سرور تغییر ایجاد میکنن با POST انجام میشن (حداقل از نظر اصولش باید اینطور باشه).

متد درخواستهای ایجکس رو هم از GET به POST تغییر دادم که اصول و امنیتش بهتر رعایت شده باشه. حداقل از نظر احتیاط ضرر نداره.

امکان دیگر هم که اضافه کردم، گزینهء لیست و مشخصات اکانتهای منتظر تایید شدن ایمیل با لینک فعال سازی است، که در اون صفحه ادمین میتونه اگر خواست هرکدام رو حذف هم بکنه (البته قاعدتا نباید این کار رو بکنه/نیازی نیست، ولی بهرحال امکانش رو گذاشتم چون برای توسعه و تست هم مفیده).