added multi language support

pull/44/head
A1Gard 4 months ago
parent d885ce07be
commit b45f3cbce7

@ -71,3 +71,8 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
XLANG_ACTIVE=false
XLANG_MAIN=en
XLANG_API_URL="http://5.255.98.77:3001"

@ -0,0 +1,327 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Controllers\XController;
use App\Http\Requests\XLangSaveRequest;
use App\Models\Access;
use App\Models\Attachment;
use App\Models\Category;
use App\Models\City;
use App\Models\Clip;
use App\Models\Discount;
use App\Models\Gallery;
use App\Models\Group;
use App\Models\Image;
use App\Models\Item;
use App\Models\Post;
use App\Models\Product;
use App\Models\Prop;
use App\Models\Setting;
use App\Models\Slider;
use App\Models\State;
use App\Models\Transport;
use App\Models\XLang;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use App\Helper;
use Illuminate\Support\Facades\Artisan;
use Spatie\Image\Image as SpatieImage;
use function App\Helpers\hasCreateRoute;
const PREFIX_PATH = __DIR__ . '/../../../../';
class XLangController extends XController
{
public $allowedModels = [
Attachment::class,
Discount::class,
Product::class,
Category::class,
Post::class,
Group::class,
Slider::class,
Item::class,
Gallery::class,
Clip::class,
Prop::class,
Setting::class,
Image::class,
State::class,
City::class,
Transport::class,
];
// protected $_MODEL_ = XLang::class;
// protected $SAVE_REQUEST = XLangSaveRequest::class;
protected $cols = ['name', 'tag', 'emoji', 'is_default'];
protected $extra_cols = ['id', 'img'];
protected $searchable = [];
protected $listView = 'admin.xlangs.xlang-list';
protected $formView = 'admin.xlangs.xlang-form';
protected $buttons = [
'edit' =>
['title' => "Edit", 'class' => 'btn-outline-primary', 'icon' => 'ri-edit-2-line'],
'show' =>
['title' => "Detail", 'class' => 'btn-outline-light', 'icon' => 'ri-eye-line'],
'destroy' =>
['title' => "Remove", 'class' => 'btn-outline-danger delete-confirm', 'icon' => 'ri-close-line'],
];
public function __construct()
{
parent::__construct(XLang::class, XLangSaveRequest::class);
}
/**
* @param $xlang XLang
* @param $request XLangSaveRequest
* @return XLang
*/
public function save($xlang, $request)
{
if ($xlang->id == null && $request->tag != 'en') {
define("TRANSLATE_CONFIG_PATH", PREFIX_PATH . 'config/translator.php');
define("TRANSLATE_NEW_FILE", PREFIX_PATH . 'resources/lang/' . $request->tag . '.json');
$config = file_get_contents(TRANSLATE_CONFIG_PATH);
$re = '/\'languages\' \=\> (.*)\,/m';
preg_match_all($re, $config, $matches, PREG_SET_ORDER, 0);
$oldLangs = $matches[0][1];
$newLans = json_encode(array_unique(array_merge(json_decode($oldLangs), [$request->tag])));
$newConfig = (str_replace($oldLangs, $newLans, $config));
file_put_contents(TRANSLATE_CONFIG_PATH, $newConfig);
if (!file_exists(TRANSLATE_NEW_FILE)) {
file_put_contents(TRANSLATE_NEW_FILE, '{}');
}
}
$xlang->name = $request->input('name');
$xlang->tag = $request->input('tag');
$xlang->rtl = $request->has('rtl');
$xlang->is_default = $request->has('is_default');
if ($xlang->is_default) {
Xlang::where('is_default', '1')->update([
'is_default' => 0,
]);
}
if (!$request->has('emoji')) {
$xlang->emoji = $request->input('emoji');
} else {
$xlang->emoji = getEmojiLanguagebyCode($xlang->tag);
}
if ($request->has('img')) {
$xlang->img = $this->storeFile('img', $xlang, 'langz');
$key = 'img';
$format = $request->file($key)->guessExtension();
if (strtolower($format) == 'png') {
$format = 'webp';
}
$i = SpatieImage::load($request->file($key)->getPathname())
->optimize()
// ->nonQueued()
->format($format);
$i->save(storage_path() . '/app/public/langz/optimized-' . $xlang->$key);
}
$xlang->save();
Artisan::call('translator:update');
return $xlang;
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
return view($this->formView);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(XLang $item)
{
//
return view($this->formView, compact('item'));
}
public function bulk(Request $request)
{
// dd($request->all());
$data = explode('.', $request->input('action'));
$action = $data[0];
$ids = $request->input('id');
switch ($action) {
case 'delete':
$msg = __(':COUNT items deleted successfully', ['COUNT' => count($ids)]);
$this->_MODEL_::destroy($ids);
break;
/**restore*/
case 'restore':
$msg = __(':COUNT items restored successfully', ['COUNT' => count($ids)]);
foreach ($ids as $id) {
$this->_MODEL_::withTrashed()->find($id)->restore();
}
break;
/*restore**/
default:
$msg = __('Unknown bulk action : :ACTION', ["ACTION" => $action]);
}
return $this->do_bulk($msg, $action, $ids);
}
public function destroy(XLang $item)
{
return parent::delete($item);
}
public function update(Request $request, XLang $item)
{
return $this->bringUp($request, $item);
}
/**restore*/
public function restore($item)
{
return parent::restoreing(XLang::withTrashed()->where('id', $item)->first());
}
/*restore**/
public function translate()
{
$langs = Xlang::all();
// return Product::where('name->en',null)->get();
return view('admin.xlangs.xlang-translates', compact('langs'));
}
public function download($tag)
{
define("TRANSLATE_FILE", PREFIX_PATH . 'resources/lang/' . $tag . '.json');
return response()->download(TRANSLATE_FILE, $tag . '.json');
}
public function upload($tag, Request $request)
{
define("TRANSLATE_FILE", PREFIX_PATH . 'resources/lang/' . $tag . '.json');
if (!$request->hasFile('json')) {
return redirect()->back();
}
$data = (file_get_contents($request->file('json')->getRealPath()));
if (json_decode($data) == null) {
return redirect()->back()->withErrors(__("Invalid json file!"));
}
file_put_contents(TRANSLATE_FILE, $data);
return redirect()->back()->with(['message' => __("Translate updated")]);
}
public function ai($tag)
{
// set_time_limit(300);
define("TRANSLATE_FILE", PREFIX_PATH . 'resources/lang/' . $tag . '.json');
$file = file_get_contents(TRANSLATE_FILE);
$url = config('app.xlang.api_url') . '/json?form=en&to=' . $tag;
$client = new Client([
'headers' => ['Content-Type' => 'application/json']
]);
$response = $client->post($url,
['body' => $file]
);
file_put_contents(TRANSLATE_FILE, $response->getBody()->getContents());
return redirect()->back()->with(['message' => __("Translated by ai xstack service :TAG", ['TAG' => $tag])]);
}
public function translateModel($id, $model)
{
if (!in_array($model, $this->allowedModels)) {
return abort(404);
}
$langs = Xlang::where('is_default', 0)->get();
$cls = $model;
$model = ($model)::where('id', $id)->firstOrFail();
// return $model;
$translates = $model->translatable;
return view('admin.xlangs.xlang-translate-model', compact('model', 'translates', 'langs', 'cls'));
}
public function translateModelSave($id, $model, Request $request)
{
if (!in_array($model, $this->allowedModels)) {
return abort(404);
}
$langs = Xlang::where('is_default', 0)->get();
$model = ($model)::where('id', $id)->firstOrFail();
// $model = Product::whereId('id',$id)->first();
foreach ($request->input('data') as $lang => $items) {
foreach ($items as $k => $item) {
if ($item != null) {
$model->setTranslation($k, $lang, $item);
}
}
}
$model->save();
return redirect()->back()->with(['message' => __('Translate updated')]);
}
public function translateModelAi($id, $model, $tag, $field)
{
if (!in_array($model, $this->allowedModels)) {
return abort(404);
}
$langs = Xlang::where('is_default', 0)->get();
$model = ($model)::where('id', $id)->firstOrFail();
// $model = Product::whereId('id',$id)->first();
$url = config('app.xlang.api_url').'/text?form=' . config('app.xlang_main') . '&to=' . $tag;
$client = new Client([
'headers' => ['Content-Type' => 'application/x-www-form-urlencoded']
]);
$response = $client->post($url,
['form_params' => ['body' => $model->$field]],
);
// file_put_contents(TRANSLATE_FILE, $response->getBody());
if ($response->getStatusCode() != 200) {
return redirect()->back()->withErrors(__("API error!"));
}
// dd($response->getBody()->getContents());
$model->setTranslation($field, $tag, $response->getBody()->getContents());
$model->save();
return redirect()->back()->with(['message' => __('Translate updated')]);
}
}

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class XLangSaveRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return auth()->check() && auth()->user()->hasRole('developer');
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required','string','min:3','max:128'],
'tag' => ['required','alpha_dash:','min:2','max:7'],
'img' => ['nullable','file','mimes:svg,png,jpg,gif'],
'emoji' => ['nullable','string']
];
}
}

@ -4,8 +4,32 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class XLang extends Model class XLang extends Model
{ {
use HasFactory; use HasFactory,SoftDeletes;
protected $table = 'xlangs';
public function imgUrl()
{
if ($this->img == null || $this->img == '') {
return asset('/assets/upload/logo.svg');
} else {
return \Storage::url('langz/optimized-' . $this->img);
}
}
public function imgOriginalUrl()
{
if ($this->img == null || $this->img == '') {
return asset('/assets/upload/logo.svg');
} else {
return \Storage::url('langz/' . $this->img);
}
}
public function getRouteKeyName(){
return 'tag';
}
} }

@ -11,6 +11,7 @@ use Carbon\Carbon;
use Illuminate\Pagination\Paginator; use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Translator\Framework\TranslatorCommand;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -20,6 +21,9 @@ class AppServiceProvider extends ServiceProvider
public function register(): void public function register(): void
{ {
// //
$this->commands([
TranslatorCommand::class,
]);
} }
/** /**

@ -152,6 +152,11 @@ return [
'watermark_size' => env('MEDIA_WATERMARK_SIZE',15), 'watermark_size' => env('MEDIA_WATERMARK_SIZE',15),
'watermark_opacity' => env('MEDIA_WATERMARK_OPACITY',50), 'watermark_opacity' => env('MEDIA_WATERMARK_OPACITY',50),
], ],
'xlang' => [
'active' => (bool) env('XLANG_ACTIVE',false),
'main' => env('XLANG_MAIN','en'),
'api_url' => env('XLANG_API_URL',''),
],

@ -4,7 +4,7 @@ use Translator\Framework\LaravelConfigLoader;
use Translator\Infra\LaravelJsonTranslationRepository; use Translator\Infra\LaravelJsonTranslationRepository;
return [ return [
'languages' => ['fa'], 'languages' => ["fa","ar"],
'directories' => [ 'directories' => [
app_path(), app_path(),
resource_path('views'), resource_path('views'),

@ -11,8 +11,16 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('x_langs', function (Blueprint $table) { Schema::create('xlangs', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name');
$table->string('tag',7)->unique();
$table->boolean('rtl')->default(false);
$table->boolean('is_default')->default(false);
$table->string('img')->nullable()->default(null);
$table->string('emoji')->nullable()->default(null);
$table->tinyInteger('sort')->default(0);
$table->softDeletes();
$table->timestamps(); $table->timestamps();
}); });
} }

@ -23,6 +23,7 @@ class DatabaseSeeder extends Seeder
'); ');
$this->call([ $this->call([
XLangSeeder::class,
UserSeeder::class, UserSeeder::class,
GroupSeeder::class, GroupSeeder::class,
PostSeeder::class, PostSeeder::class,

@ -2,6 +2,7 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\XLang;
use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -13,5 +14,29 @@ class XLangSeeder extends Seeder
public function run(): void public function run(): void
{ {
// //
$langz = [
'en' => 'English',
'ar' => 'عربي',
'es' => 'Spanish',
'fr' => 'French',
'pt' => 'Portuguese',
'fa' => 'پارسی',
'ru' => 'Русский',
'de' => 'Deutsch',
'ro' => 'Romanian',
'it' => 'Italian',
'zh'=> '简化字',
];
if (config('app.xlang.active')) {
$lang = new XLang();
$lang->tag = config('app.xlang.main');
$lang->emoji = getEmojiLanguagebyCode(config('app.xlang.main'));
$lang->name = $langz[config('app.xlang.main')]??config('app.xlang.main');
$lang->rtl = langIsRTL(config('app.xlang.main'));
$lang->is_default = true;
$lang->save();
}
} }
} }

@ -1,11 +1,17 @@
window.addEventListener('load', function () { window.addEventListener('load', function () {
let dirx = 'ltr'; let dirx = document.querySelector('#panel-dir').value;
let editors = {}; let editors = {};
document.querySelectorAll('.ckeditorx')?.forEach(function (el) { document.querySelectorAll('.ckeditorx')?.forEach(function (el) {
const currentDir = el.getAttribute('dir');
let finalDir = dirx;
if (currentDir != null){
finalDir = currentDir;
}
editors[el.getAttribute('name')] = CKEDITOR.replace(el.getAttribute('name'), { editors[el.getAttribute('name')] = CKEDITOR.replace(el.getAttribute('name'), {
filebrowserUploadUrl: xupload, filebrowserUploadUrl: xupload,
filebrowserUploadMethod: 'form', filebrowserUploadMethod: 'form',
contentsLangDirection: dirx, contentsLangDirection: finalDir,
skin: 'moono-dark', skin: 'moono-dark',
}); });

@ -0,0 +1,58 @@
{
" days ago": "منذ أيام",
" hours ago": "منذ ساعات",
" minutes ago": "منذ دقائق",
" seconds ago": "منذ ثواني",
"$role": "$ دور",
":ADDRESS removed": ": تمت إزالة العنوان",
":COUNT Images uploaded successfully": ":تم تحميل COUNT من الصور بنجاح",
":COUNT items answered successfully": ":تم الرد على COUNT من العناصر بنجاح",
":COUNT items changed status successfully": ": تم تغيير حالة COUNT من العناصر بنجاح",
":COUNT items closed successfully": ":تم إغلاق COUNT من العناصر بنجاح",
":COUNT items deleted successfully": ":تم حذف COUNT من العناصر بنجاح",
":COUNT items drafted successfully": ": تمت صياغة COUNT من العناصر بنجاح",
":COUNT items pending successfully": ":COUNT من العناصر المعلقة بنجاح",
":COUNT items published successfully": ":تم نشر COUNT من العناصر بنجاح",
":COUNT items restored successfully": ": تمت استعادة COUNT من العناصر بنجاح",
":COUNT users role changed to :NEWROLE successfully": ":تم تغيير دور COUNT من المستخدمين إلى :COUNT بنجاح",
"A fresh verification link has been sent to your email address.": "تم إرسال رابط تحقق جديد إلى عنوان بريدك الإلكتروني.",
"Add": "أضف",
"Add another one": "أضف واحدًا آخر",
"Add cover to better results": "أضف غطاء لنتائج أفضل",
"Add new adv": "إضافة إعلان جديد",
"Add new attachment": "إضافة مرفق جديد",
"Add new category": "إضافة تصنيف جديد",
"Add new city": "إضافة مدينة جديدة",
"Add new clip": "إضافة مقطع جديد",
"Add new comment": "إضافة تعليق جديد",
"Add new contact": "إضافة جهة اتصال جديدة",
"Add new customer": "إضافة عميل جديد",
"Add new discount": "إضافة خصم جديد",
"Add new gallery": "إضافة معرض جديد",
"Add new group": "إضافة مجموعة جديدة",
"Add new language": "إضافة لغة جديدة",
"Add new post": "إضافة مشاركة جديدة",
"Add new product": "إضافة منتج جديد",
"Add new prop": "إضافة دعامة جديدة",
"Add new question": "إضافة سؤال جديد",
"Add new setting": "إضافة إعداد جديد",
"Add new slider": "إضافة شريط تمرير جديد",
"Add new state": "إضافة حالة جديدة",
"Add new ticket": "إضافة تذكرة جديدة",
"Add new transport": "إضافة وسيلة نقل جديدة",
"Add new user": "إضافة مستخدم جديد",
"Add to setting": "أضف إلى الإعداد",
"Added by:": "أضيفت بواسطة :",
"Additional data": "بيانات إضافية",
"Address added to :CUSTOMER": "تم إضافة العنوان إلى :CUSTOMER",
"Addresses": "العناوين",
"Advertise": "أعلن",
"Advs list": "قائمة الإعلانات",
"Amount": "المبلغ",
"Answer": "الإجابة",
"Answered": "تمت الإجابة",
"Approve": "موافقة",
"Approved": "تمت الموافقة",
"Area design": "تصميم المنطقة",
"yesterday": "أمس"
}

@ -30,6 +30,7 @@
"Add new discount": "افزودن یک تخفیف", "Add new discount": "افزودن یک تخفیف",
"Add new gallery": "افزودن یک گالری", "Add new gallery": "افزودن یک گالری",
"Add new group": "افزودن یک سرفصل", "Add new group": "افزودن یک سرفصل",
"Add new language": "افزودن یک زبان جدید",
"Add new post": "افزودن یک نوشته", "Add new post": "افزودن یک نوشته",
"Add new product": "افزودن یک محصول", "Add new product": "افزودن یک محصول",
"Add new prop": "افزودن یک ویژگی", "Add new prop": "افزودن یک ویژگی",
@ -103,6 +104,7 @@
"Dashboard": "پیشخوان", "Dashboard": "پیشخوان",
"Date": "تاریخ", "Date": "تاریخ",
"Deattach": "عدم پیوست", "Deattach": "عدم پیوست",
"Default": "پیش‌فرض",
"Description": "توضیحات", "Description": "توضیحات",
"Description Table": "جدول توضیحات", "Description Table": "جدول توضیحات",
"Description Text": "نوشته توضیحات", "Description Text": "نوشته توضیحات",
@ -115,6 +117,7 @@
"Do it": "انجام بده", "Do it": "انجام بده",
"Double click on image to change index image": "برای تغییر تصویر شاخص روی آن جفت کلیک کنید", "Double click on image to change index image": "برای تغییر تصویر شاخص روی آن جفت کلیک کنید",
"Download": "بارگیری", "Download": "بارگیری",
"Download json file": "",
"Draft": "پیش‌نویس", "Draft": "پیش‌نویس",
"E-mail is unique each users": "رایانامه هر کاربر یکتا است", "E-mail is unique each users": "رایانامه هر کاربر یکتا است",
"Edit": "ویرایش", "Edit": "ویرایش",
@ -129,6 +132,7 @@
"Edit discount": "ویرایش تخفیف", "Edit discount": "ویرایش تخفیف",
"Edit gallery": "ویرایش گالری", "Edit gallery": "ویرایش گالری",
"Edit group": "ویرایش سرفصل", "Edit group": "ویرایش سرفصل",
"Edit language": "ویرایش زبان",
"Edit post": "ویرایش نوشته", "Edit post": "ویرایش نوشته",
"Edit product": "ویرایش محصول", "Edit product": "ویرایش محصول",
"Edit prop": "ویرایش ویژگی", "Edit prop": "ویرایش ویژگی",
@ -140,6 +144,7 @@
"Edit user": "ویرایش کاربر", "Edit user": "ویرایش کاربر",
"Email": "رایانامه", "Email": "رایانامه",
"Email Address": "نشانی ایمیل", "Email Address": "نشانی ایمیل",
"Emoji": "ایموجی",
"Excerpt": "خلاصه", "Excerpt": "خلاصه",
"Expire": "انقضا", "Expire": "انقضا",
"Expire date": "تاریخ انقضا", "Expire date": "تاریخ انقضا",
@ -150,6 +155,7 @@
"File ext": "پسوند پرونده", "File ext": "پسوند پرونده",
"File name": "نام پرونده", "File name": "نام پرونده",
"File size": "حجم پرونده", "File size": "حجم پرونده",
"Flag": "پرچم",
"Forgot Your Password?": "آیا گذرواژه خود را فراموش کردید", "Forgot Your Password?": "آیا گذرواژه خود را فراموش کردید",
"From - To": "از - تا", "From - To": "از - تا",
"GFX of website updated": "گرافیک سایت به روز شد", "GFX of website updated": "گرافیک سایت به روز شد",
@ -174,6 +180,7 @@
"Information": "اطلاعات", "Information": "اطلاعات",
"Interaction": "تعامل", "Interaction": "تعامل",
"Invalid area segment": "", "Invalid area segment": "",
"Invalid json file!": "",
"Invalid morph": "چند ریخیتی نا معتبر", "Invalid morph": "چند ریخیتی نا معتبر",
"Invoices": "صورت‌حساب‌ها", "Invoices": "صورت‌حساب‌ها",
"Is default": "آی پیش‌فرض است", "Is default": "آی پیش‌فرض است",
@ -182,6 +189,7 @@
"Key": "کلید", "Key": "کلید",
"Label": "برچسب", "Label": "برچسب",
"Languages": "زبان‌ها", "Languages": "زبان‌ها",
"Languages list": "فهرست زبان‌ها",
"Latitude": "عرض جغرافیایی", "Latitude": "عرض جغرافیایی",
"Link": "پیوند", "Link": "پیوند",
"Login": "ورود", "Login": "ورود",
@ -230,6 +238,7 @@
"Question\/Message": "سوال|پیام", "Question\/Message": "سوال|پیام",
"Questions": "سوالات", "Questions": "سوالات",
"Questions list": "فهرست سوالات", "Questions list": "فهرست سوالات",
"RTL": "راست به چپ",
"Recommends": "توصیه‌ها", "Recommends": "توصیه‌ها",
"Register": "ثبت‌نام", "Register": "ثبت‌نام",
"Reject": "رد کردن", "Reject": "رد کردن",
@ -276,6 +285,7 @@
"Stock quantity": "موجود انبار", "Stock quantity": "موجود انبار",
"Subject": "عنوان", "Subject": "عنوان",
"Subtitle": "زیرعنوان", "Subtitle": "زیرعنوان",
"Tag": "برچسب",
"Tags": "برچسب‌ها", "Tags": "برچسب‌ها",
"Tags, Press enter": "برچسب‌ها، Enter را بزنید", "Tags, Press enter": "برچسب‌ها، Enter را بزنید",
"The first and\/or second image will be index image": "اولی وی دومی به عنواان تصویر شاخص در نظر گرفته می‌شود", "The first and\/or second image will be index image": "اولی وی دومی به عنواان تصویر شاخص در نظر گرفته می‌شود",
@ -291,6 +301,8 @@
"Toggle navigation": "", "Toggle navigation": "",
"Toggle selection": "برعکس کردن انتخاب", "Toggle selection": "برعکس کردن انتخاب",
"Totol": "همه", "Totol": "همه",
"Translate updated": "",
"Translate with AI": "",
"Transports": "حمل و نقل‌ها", "Transports": "حمل و نقل‌ها",
"Transports list": "فهرست حمل و نقل‌ها", "Transports list": "فهرست حمل و نقل‌ها",
"Trashed": "حذف شده‌ها", "Trashed": "حذف شده‌ها",
@ -299,6 +311,7 @@
"Type": "نوع", "Type": "نوع",
"Unit": "واحد", "Unit": "واحد",
"Unknown bulk action : :ACTION": "کار دسته جمعی تعریف نشده :ACTION", "Unknown bulk action : :ACTION": "کار دسته جمعی تعریف نشده :ACTION",
"Upload file": "",
"Upload images": "بارگزاری تصاویر", "Upload images": "بارگزاری تصاویر",
"Upload new images": "بارگزاری تصاویر جدید", "Upload new images": "بارگزاری تصاویر جدید",
"User filter": "صافی کاربر", "User filter": "صافی کاربر",
@ -321,6 +334,7 @@
"approved": "تایید شد", "approved": "تایید شد",
"area :NAME of website updated": "محیط :NAME وبسایت به روز شد", "area :NAME of website updated": "محیط :NAME وبسایت به روز شد",
"click here to request another": "برای ایجاد درخواست دیگر اینجا کلیک کنید", "click here to request another": "برای ایجاد درخواست دیگر اینجا کلیک کنید",
"emoji": "ایموجی",
"image": "تصویر", "image": "تصویر",
"jpg": "", "jpg": "",
"minute": "دقیق", "minute": "دقیق",

@ -110,11 +110,11 @@
<tr> <tr>
<th> <th>
<div <div
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
data-bs-custom-class="custom-tooltip" data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Check all")}}" data-bs-title="{{__("Check all")}}"
class="form-check form-switch mt-1 mx-2"> class="form-check form-switch mt-1 mx-2">
<input class="form-check-input chkall" <input class="form-check-input chkall"
type="checkbox" role="switch"> type="checkbox" role="switch">
</div> </div>
@ -158,7 +158,7 @@
@if(isset($item) && method_exists($item,'imgUrl')) @if(isset($item) && method_exists($item,'imgUrl'))
<td> <td>
<a href="{{getRoute('edit',$item->{$item->getRouteKeyName()})}}"> <a href="{{getRoute('edit',$item->{$item->getRouteKeyName()})}}">
<img src="{{$item->imgUrl()}}" class="image-x64" alt=""> <img src="{{$item->imgUrl()}}" class="image-x64" alt="">
</a> </a>
</td> </td>
@endif @endif
@ -257,6 +257,15 @@
</a> </a>
</li> </li>
@endforeach @endforeach
@if(config('app.xlang.active') && isset($item->translatable))
<li>
<a class="dropdown-item" href="{{route('admin.lang.model',[$item->id, get_class($item)])}}">
<i class="ri-translate"></i>
&nbsp;
{{__("Translate")}}
</a>
</li>
@endif
</ul> </ul>
</div> </div>
@endif @endif
@ -298,6 +307,12 @@
@endif @endif
@endif @endif
@endforeach @endforeach
@if(config('app.xlang.active') && isset($item->translatable))
<a href="{{route('admin.lang.model',[$item->id, get_class($item)])}}"
class="btn btn-outline-secondary translat-btn btn-sm mx-1">
<i class="ri-translate"></i>
</a>
@endif
</div> </div>
</td> </td>
</tr> </tr>
@ -315,12 +330,12 @@
<div class="row"> <div class="row">
<div class="col-md-3 text-start"> <div class="col-md-3 text-start">
<div <div
id="toggle-select" id="toggle-select"
class="btn btn-outline-light mx-2" class="btn btn-outline-light mx-2"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-placement="top" data-bs-placement="top"
data-bs-custom-class="custom-tooltip" data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Toggle selection")}}"> data-bs-title="{{__("Toggle selection")}}">
<i class="ri-toggle-line"></i> <i class="ri-toggle-line"></i>
</div> </div>
</div> </div>

@ -0,0 +1,105 @@
@extends('admin.templates.panel-form-template')
@section('title')
@if(isset($item))
{{__("Edit language")}} [{{$item->tag}}]
@else
{{__("Add new language")}}
@endif -
@endsection
@section('form')
<div class="row">
<div class="col-lg-3">
@include('components.err')
<div class="item-list mb-3">
<h3 class="p-3">
<i class="ri-message-3-line"></i>
{{__("Tips")}}
</h3>
<ul>
<li>
{{__("Recommends")}}
</li>
</ul>
</div>
</div>
<div class="col-lg-9 ps-xl-1 ps-xxl-1">
<div class="general-form ">
<h1>
@if(isset($item))
{{__("Edit language")}} [{{$item->tag}}]
@else
{{__("Add new language")}}
@endif
</h1>
<div class="row">
<div class="col-md-8 mt-3">
<div class="form-group">
<label for="name" >
{{__('Name')}}
</label>
<input name="name" type="text" class="form-control @error('name') is-invalid @enderror" id="name" placeholder="{{__('Name')}}" value="{{old('name',$item->name??null)}}" />
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="tag" >
{{__('Tag')}}
</label>
<input name="tag" type="text" class="form-control @error('tag') is-invalid @enderror" id="tag" placeholder="{{__('Tag')}}" value="{{old('tag',$item->tag??null)}}" maxlength="7" />
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="emoji" >
{{__('Emoji')}}
</label>
<input name="emoji" type="text" class="form-control @error('emoji') is-invalid @enderror" id="emoji" placeholder="{{__('emoji')}}" value="{{old('emoji',$item->emoji??null)}}" maxlength="4" />
</div>
</div>
<div class="col-md-4 mt-3">
<div class="form-group">
<label for="flag" >
{{__('Image')}}
</label>
<input name="img" type="file" class="form-control @error('img') is-invalid @enderror" id="flag" placeholder="{{__('Flag')}}" />
</div>
</div>
<div class="col-md-2 mt-3">
<div class="form-check form-switch mt-1">
<br>
<input class="form-check-input @error('rtl') is-invalid @enderror"
name="rtl" type="checkbox" id="rtl" @if(old('rtl',$item->rtl??null) == 1) checked="" @endif
value="1" >
<label for="rtl">
{{__('RTL')}}
</label>
</div>
</div>
<div class="col-md-2 mt-3">
<div class="form-check form-switch mt-1">
<br>
<input class="form-check-input @error('is_default') is-invalid @enderror"
name="is_default" type="checkbox" id="is_default" @if(old('is_default',$item->is_default??null) == 1) checked="" @endif
value="1" >
<label for="is_default">
{{__('Default')}}
</label>
</div>
</div>
<div class="col-12 mt-4">
<label> &nbsp; </label>
<input name="" id="" type="submit" class="btn btn-primary mt-2" value="{{__('Save')}}" />
</div>
</div>
</div>
</div>
</div>
@endsection

@ -0,0 +1,28 @@
@extends('admin.templates.panel-list-template')
@section('list-title')
<i class="ri-user-3-line"></i>
{{__("Languages list")}}
@endsection
@section('title')
{{__("Languages list")}} -
@endsection
@section('filter')
{{-- Other filters --}}
@endsection
@section('bulk')
{{-- <option value="-"> - </option> --}}
@endsection
@section('list-foot')
<a
href="{{getRoute('translate')}}"
class="action-btn circle-btn"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-custom-class="custom-tooltip"
data-bs-title="{{__("Sort")}}"
style="inset-inline-end: 1.2rem;inset-inline-start: auto;"
>
<i class="ri-translate-2"></i>
</a>
@endsection

@ -0,0 +1,91 @@
@extends('layouts.app')
@section('title')
{{__("Translate model")}}: {{($model->{$translates[0]})}}
-
@endsection
@section('content')
@include('components.err')
<div class="general-form mb-3">
<h1>
{{__("Translate model")}}: {{($model->{$translates[0]})}}
</h1>
<h4 class="lang-support p-3">
{{__("Main language content")}}:({{config('app.xlang.main')}})
</h4>
<div class="p-3">
<table class="table">
<tr>
<th class="w-25">
{{__("Title")}}
</th>
<th>
{{__("Value")}}
</th>
</tr>
@foreach($translates as $tr)
<tr>
<th>
{{$tr}}
</th>
<td>
{{($model->{$tr})}}
</td>
</tr>
@endforeach
</table>
</div>
</div>
<form action="{{route( 'admin.lang.modelSave',[$model->id,$cls])}}" method="post">
@csrf
@foreach($langs as $lang)
<div class="general-form mb-3">
<h1 class="lang-support">
{{__("Translate model")}}: {{$lang->name}} ({{$lang->tag}})
</h1>
@foreach($translates as $tr)
<div class="form-group px-3 pt-2">
<label for="{{$lang->tag}}{{$tr}}">
{{$tr}}:
</label>
<div class="row">
<div class="col-md-10">
@if( $tr == 'body' || $tr == 'desc' || $tr == 'description' || $tr == 'excerpt' || $tr == 'table')
<textarea
@if(langIsRTL($lang->tag)) dir="rtl" @else dir="ltr" @endif
class="form-control @if($tr == 'body' || $tr == 'desc' || $tr == 'description' || $tr == 'table' ) ckeditorx @endif"
rows="4" id="{{$lang->tag}}{{$tr}}"
name="data[{{$lang->tag}}][{{$tr}}]">{{gettype($model->getTranslation($tr,$lang->tag)) == 'string' ? $model->getTranslation($tr,$lang->tag):'' }}</textarea>
@else
<input @if(langIsRTL($lang->tag)) dir="rtl" @else dir="ltr" @endif type="text"
id="{{$lang->tag}}{{$tr}}" name="data[{{$lang->tag}}][{{$tr}}]"
value="{{gettype($model->getTranslation($tr,$lang->tag)) == 'string' ? $model->getTranslation($tr,$lang->tag):'' }}"
placeholder="" class="form-control">
@endif
</div>
<div class="col-md-2 d-flex align-items-center">
<a href="{{route('admin.lang.aiText',[$model->id,$cls,$lang->tag,$tr])}}" class="btn-ai"
title="{{__("AI translate form original source")}}">
<i class="ri-translate"></i>
</a>
</div>
</div>
</div>
@endforeach
</div>
@endforeach
<button class="btn btn-outline-dark w-100 my-3">
{{__("Save")}}
</button>
</form>
@endsection

@ -0,0 +1,45 @@
@extends('layouts.app')
@section('content')
@include('components.err')
<div class="row">
@foreach($langs as $lang)
<div class="col-md-4 mb-4">
<div class="item-list p-3">
<h5 class="text-center">
{{$lang->name}} [ {{$lang->emoji}} ]
</h5>
<img src="{{$lang->imgUrl()}}" class="img-fluid my-3" alt="{{$lang->tag}}">
@if($lang->tag != 'en')
<a href="{{route('admin.lang.download',$lang->tag)}}"
class="btn btn-outline-light w-100 mb-3 btn-sm">
<i class="ri-download-2-line"></i>
{{__("Download json file")}}
</a>
@else
<a class="btn btn-outline-light w-100 mb-3 btn-sm disabled">
<i class="ri-download-2-line"></i>
{{__("Download json file")}}
</a>
@endif
<form action="{{route('admin.lang.upload',$lang->tag)}}" method="post"
enctype="multipart/form-data">
@csrf
<input type="file" name="json" id="file" class="form-control mb-2">
<button class="btn btn-outline-primary btn-sm w-100 mt-2 " @if($lang->tag == 'en') disabled @endif>
<i class="ri-upload-2-line"></i>
{{__("Upload file")}}
</button>
</form>
<a @if($lang->tag != 'en') href="{{route('admin.lang.ai',$lang->tag)}}" @endif class="btn btn-outline-success w-100 mt-3 btn-sm ">
<i class="ri-ai-generate"></i>
{{__("Translate with AI")}}
</a>
</div>
</div>
@endforeach
</div>
@endsection

@ -218,12 +218,14 @@
{{__('Logs of guests')}} {{__('Logs of guests')}}
</a> </a>
</li> </li>
<li> @if(auth()->user()->hasRole('developer') && config('app.xlang.active'))
<a> <li>
<i class="ri-global-fill"></i> <a href="{{ route('admin.lang.index') }}">
{{__("Languages")}} <i class="ri-global-fill"></i>
</a> {{__("Languages")}}
</li> </a>
</li>
@endif
</ul> </ul>
</li> </li>
<li data-bs-toggle="tooltip" data-bs-placement="auto" data-bs-custom-class="custom-tooltip" <li data-bs-toggle="tooltip" data-bs-placement="auto" data-bs-custom-class="custom-tooltip"

@ -1,6 +1,8 @@
@include('components.panel-header') @include('components.panel-header')
<div id="app"> <div id="app">
<input type="hidden" id="panel-dir" @if(langIsRTL(config('app.locale'))) value="rtl" @else value="ltr" @endif>
@include('components.panel-top-navbar') @include('components.panel-top-navbar')
<div> <div>

@ -1,5 +1,6 @@
<?php <?php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::get('/', function () { Route::get('/', function () {
@ -313,12 +314,35 @@ Route::prefix(config('app.panel.prefix'))->name('admin.')->group(
); );
}); });
Route::prefix('langs')->name('lang.')->group(
function () {
Route::get('/', [\App\Http\Controllers\Admin\XLangController::class, 'index'])->name('index');
Route::get('/translates', [\App\Http\Controllers\Admin\XLangController::class, 'translate'])->name('translate');
Route::get('/delete/{item}', [\App\Http\Controllers\Admin\XLangController::class, 'destroy'])->name('delete');
Route::get('/create', [\App\Http\Controllers\Admin\XLangController::class, 'create'])->name('create');
Route::post('/store', [\App\Http\Controllers\Admin\XLangController::class, 'store'])->name('store');
Route::get('/edit/{item}', [\App\Http\Controllers\Admin\XLangController::class, 'edit'])->name('edit');
Route::post('/update/{item}', [\App\Http\Controllers\Admin\XLangController::class, 'update'])->name('update');
Route::post('bulk', [\App\Http\Controllers\Admin\XLangController::class, "bulk"])->name('bulk');
Route::get('/download/{tag}', [\App\Http\Controllers\Admin\XLangController::class, 'download'])->name('download');
Route::get('/ai/{tag}', [\App\Http\Controllers\Admin\XLangController::class, 'ai'])->name('ai');
Route::post('/upload/{tag}', [\App\Http\Controllers\Admin\XLangController::class, 'upload'])->name('upload');
Route::get('/model/translate/{id}/{model}', [\App\Http\Controllers\Admin\XLangController::class, 'translateModel'])->name('model');
Route::post('/model/translate/save/{id}/{model}', [\App\Http\Controllers\Admin\XLangController::class, 'translateModelSave'])->name('modelSave');
Route::get('/model/ai/{id}/{model}/{field}/{lang}', [\App\Http\Controllers\Admin\XLangController::class, 'translateModelAi'])->name('aiText');
Route::get('restore/{item}', [\App\Http\Controllers\Admin\XLangController::class, 'restore'])->name('restore');
Route::get('trashed', [\App\Http\Controllers\Admin\XLangController::class, "trashed"])->name('trashed');
});
}); });
Route::get('test',function (){ Route::get('test',function (){
// return \Resources\Views\Segments\PreloaderCircle::onAdd(); // return \Resources\Views\Segments\PreloaderCircle::onAdd();
Log::info('--test--');
$i = \App\Models\Product::first();
return get_class($i);
Log::info('--test--');
}); });

Loading…
Cancel
Save