diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index e2c2ac56..3c56fcc2 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -45,7 +45,11 @@ class Test extends Command public function handle() { $rep = new ExamRepository(); - $r = $rep->addProgress(15, 1, 250, 1); - dd($r); +// $r = $rep->assignToUser(1, 1); +// $r = $rep->addProgress(27, 4, 1025, 1); +// dd($r); +// $rep->assignCronjob(); + $rep->cronjobCheckout(); } + } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 567176cc..d8320799 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -63,5 +63,6 @@ class Kernel extends HttpKernel 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'permission' => \App\Http\Middleware\Permission::class, + 'locale' => \App\Http\Middleware\Locale::class, ]; } diff --git a/app/Http/Middleware/Locale.php b/app/Http/Middleware/Locale.php new file mode 100644 index 00000000..8eaa5381 --- /dev/null +++ b/app/Http/Middleware/Locale.php @@ -0,0 +1,32 @@ + 'en', + 'chs' => 'zh_CN', + 'cht' => 'zh_TW', + ]; + + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $language = $request->user()->language; + $locale = self::$languageMaps[$language->site_lang_folder] ?? 'en'; + do_log("set locale: " . $locale); + App::setLocale($locale); + return $next($request); + } +} diff --git a/app/Http/Middleware/Permission.php b/app/Http/Middleware/Permission.php index 1f4043cc..3131dfa8 100644 --- a/app/Http/Middleware/Permission.php +++ b/app/Http/Middleware/Permission.php @@ -25,7 +25,7 @@ class Permission do_log("$log, denied!"); throw new UnauthorizedException('Unauthorized!'); } - do_log("$log, pass!"); + do_log("$log, allow!"); return $next($request); } } diff --git a/app/Http/Resources/ExamUserResource.php b/app/Http/Resources/ExamUserResource.php index ce1989f7..48c484f1 100644 --- a/app/Http/Resources/ExamUserResource.php +++ b/app/Http/Resources/ExamUserResource.php @@ -23,11 +23,11 @@ class ExamUserResource extends JsonResource 'id' => $this->id, 'status' => $this->status, 'status_text' => $this->statusText, - 'created_at' => formatDatetime($this->created_at), + 'created_at' => format_datetime($this->created_at), 'progress' => $this->when($this->progress, $this->progress), 'progress_formatted' => $this->when($this->progress_formatted, $this->progress_formatted), - 'begin' => formatDatetime($this->begin), - 'end' => formatDatetime($this->end), + 'begin' => format_datetime($this->begin), + 'end' => format_datetime($this->end), 'uid' => $this->uid, 'exam_id' => $this->exam_id, 'user' => new UserResource($this->whenLoaded('user')), diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php index 27a4932d..cf7c9835 100644 --- a/app/Http/Resources/UserResource.php +++ b/app/Http/Resources/UserResource.php @@ -19,7 +19,7 @@ class UserResource extends JsonResource 'email' => $this->email, 'username' => $this->username, 'status' => $this->status, - 'added' => formatDatetime($this->added), + 'added' => format_datetime($this->added), 'class' => $this->class, 'class_text' => $this->class_text, 'avatar' => $this->avatar, diff --git a/app/Models/ExamUser.php b/app/Models/ExamUser.php index b9b41375..5ba1855c 100644 --- a/app/Models/ExamUser.php +++ b/app/Models/ExamUser.php @@ -4,7 +4,7 @@ namespace App\Models; class ExamUser extends NexusModel { - protected $fillable = ['exam_id', 'uid', 'status', 'progress', 'begin', 'end']; + protected $fillable = ['exam_id', 'uid', 'status', 'progress', 'begin', 'end', 'is_done']; public $timestamps = true; @@ -16,6 +16,10 @@ class ExamUser extends NexusModel self::STATUS_FINISHED => ['text' => 'Finished'], ]; + const IS_DONE_YES = 1; + const IS_DONE_NO = 0; + + protected $casts = [ 'progress' => 'json' ]; diff --git a/app/Models/Language.php b/app/Models/Language.php new file mode 100644 index 00000000..8b0deb76 --- /dev/null +++ b/app/Models/Language.php @@ -0,0 +1,12 @@ +hasMany(ExamUser::class, 'uid'); } + public function language() + { + return $this->belongsTo(Language::class, 'lang'); + } + } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0426947e..faa701f6 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; use Illuminate\Support\ServiceProvider; use Illuminate\Http\Resources\Json\JsonResource; diff --git a/app/Repositories/ExamRepository.php b/app/Repositories/ExamRepository.php index 9901935e..5670bff1 100644 --- a/app/Repositories/ExamRepository.php +++ b/app/Repositories/ExamRepository.php @@ -5,10 +5,12 @@ use App\Exceptions\NexusException; use App\Models\Exam; use App\Models\ExamProgress; use App\Models\ExamUser; +use App\Models\Message; use App\Models\Setting; use App\Models\Torrent; use App\Models\User; use Carbon\Carbon; +use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; @@ -25,6 +27,10 @@ class ExamRepository extends BaseRepository public function store(array $params) { $this->checkIndexes($params); + $valid = $this->listValid(); + if ($valid->isNotEmpty()) { + throw new NexusException("Valid exam already exists."); + } $exam = Exam::query()->create($params); return $exam; } @@ -32,6 +38,10 @@ class ExamRepository extends BaseRepository public function update(array $params, $id) { $this->checkIndexes($params); + $valid = $this->listValid($id); + if ($valid->isNotEmpty()) { + throw new NexusException("Valid exam already exists."); + } $exam = Exam::query()->findOrFail($id); $exam->update($params); return $exam; @@ -78,16 +88,21 @@ class ExamRepository extends BaseRepository /** * list valid exams * + * @param null $excludeId * @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection */ - public function listValid() + public function listValid($excludeId = null) { $now = Carbon::now(); - return Exam::query() + $query = Exam::query() ->where('begin', '<=', $now) ->where('end', '>=', $now) ->where('status', Exam::STATUS_ENABLED) - ->get(); + ->orderBy('id', 'desc'); + if ($excludeId) { + $query->whereNotIn('id', Arr::wrap($excludeId)); + } + return $query->get(); } /** @@ -104,48 +119,55 @@ class ExamRepository extends BaseRepository do_log("$logPrefix, no valid exam."); return $exams; } - $user = User::query()->findOrFail($uid, ['id', 'username', 'added', 'class']); - $filtered = $exams->filter(function (Exam $exam) use ($user, $logPrefix) { - $filters = $exam->filters; - if (empty($filters->classes)) { - do_log("$logPrefix, exam: {$exam->id} no class"); - return false; - } - if (!in_array($user->class, $filters->classes)) { - do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); - return false; - } - if (!$user->added) { - do_log("$logPrefix, user no added time", 'warning'); - return false; - } - - $added = $user->added->toDateTimeString(); - $registerTimeBegin = $filters->register_time_range[0] ? Carbon::parse($filters->register_time_range[0])->toDateString() : ''; - $registerTimeEnd = $filters->register_time_range[1] ? Carbon::parse($filters->register_time_range[1])->toDateString() : ''; - if (empty($registerTimeBegin)) { - do_log("$logPrefix, exam: {$exam->id} no register_time_begin"); - return false; - } - if ($added < $registerTimeBegin) { - do_log("$logPrefix, added: $added not after: " . $registerTimeBegin); - return false; - } - - if (empty($registerTimeEnd)) { - do_log("$logPrefix, exam: {$exam->id} no register_time_end"); - return false; - } - if ($added > $registerTimeEnd) { - do_log("$logPrefix, added: $added not before: " . $registerTimeEnd); - return false; - } - - return true; + $matched = $exams->filter(function (Exam $exam) use ($uid, $logPrefix) { + return $this->isExamMatchUser($exam, $uid); }); - return $filtered; + return $matched; + } + + private function isExamMatchUser(Exam $exam, $user) + { + if (!$user instanceof User) { + $user = User::query()->findOrFail(intval($user), ['id', 'username', 'added', 'class']); + } + $logPrefix = sprintf('exam: %s, user: %s', $exam->id, $user->id); + $filters = $exam->filters; + if (empty($filters->classes)) { + do_log("$logPrefix, exam: {$exam->id} no class"); + return false; + } + if (!in_array($user->class, $filters->classes)) { + do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); + return false; + } + if (!$user->added) { + do_log("$logPrefix, user no added time", 'warning'); + return false; + } + + $added = $user->added->toDateTimeString(); + $registerTimeBegin = $filters->register_time_range[0] ? Carbon::parse($filters->register_time_range[0])->toDateString() : ''; + $registerTimeEnd = $filters->register_time_range[1] ? Carbon::parse($filters->register_time_range[1])->toDateString() : ''; + if (empty($registerTimeBegin)) { + do_log("$logPrefix, exam: {$exam->id} no register_time_begin"); + return false; + } + if ($added < $registerTimeBegin) { + do_log("$logPrefix, added: $added not after: " . $registerTimeBegin); + return false; + } + + if (empty($registerTimeEnd)) { + do_log("$logPrefix, exam: {$exam->id} no register_time_end"); + return false; + } + if ($added > $registerTimeEnd) { + do_log("$logPrefix, added: $added not before: " . $registerTimeEnd); + return false; + } + return true; } @@ -244,8 +266,16 @@ class ExamRepository extends BaseRepository do_log("$logPrefix [addProgress] " . nexus_json_encode($data)); $newProgress = $examUser->progresses()->create($data); $examProgress = $this->calculateProgress($examUser); - do_log("$logPrefix [updateProgress] " . nexus_json_encode($examProgress)); - $examUser->update(['progress' => $examProgress]); + $examProgressFormatted = $this->getProgressFormatted($exam, $examProgress); + $examNotPassed = array_filter($examProgressFormatted, function ($item) { + return !$item['passed']; + }); + $update = [ + 'progress' => $examProgress, + 'is_done' => count($examNotPassed) ? ExamUser::IS_DONE_NO : ExamUser::IS_DONE_YES, + ]; + do_log("$logPrefix [updateProgress] " . nexus_json_encode($update)); + $examUser->update($update); return $newProgress; } @@ -253,7 +283,7 @@ class ExamRepository extends BaseRepository { $logPrefix = "uid: $uid"; $query = ExamUser::query()->where('uid', $uid)->orderBy('exam_id', 'desc'); - if ($status) { + if (!is_null($status)) { $query->where('status', $status); } if (!empty($with)) { @@ -310,7 +340,7 @@ class ExamRepository extends BaseRepository ->groupBy(['index']) ->get(); - do_log("$logPrefix, query: " . last_query() . ", progressSum: " . $progressSum->toJson()); + do_log("$logPrefix, " . last_query() . ", progressSum: " . $progressSum->toJson()); return $progressSum->pluck('sum', 'index')->toArray(); @@ -339,7 +369,8 @@ class ExamRepository extends BaseRepository $currentValueFormatted = $currentValue; $requireValueAtomic = $requireValue; } - $index['require_value_formatted'] = "$requireValue {$index['unit']}"; + $index['index_formatted'] = nexus_trans('exam.index_text_' . $index['index']); + $index['require_value_formatted'] = "$requireValue " . ($index['unit'] ?? ''); $index['current_value'] = $currentValue; $index['current_value_formatted'] = $currentValueFormatted; $index['passed'] = $currentValue >= $requireValueAtomic; @@ -359,6 +390,119 @@ class ExamRepository extends BaseRepository return $result; } + public function cronjonAssign() + { + $exams = $this->listValid(); + if ($exams->isEmpty()) { + do_log("No valid exam."); + return true; + } + if ($exams->count() > 1) { + do_log("Valid exam more than 1.", "warning"); + } + /** @var Exam $exam */ + $exam = $exams->first(); + $userTable = (new User())->getTable(); + $examUserTable = (new ExamUser())->getTable(); + User::query() + ->leftJoin($examUserTable, function (JoinClause $join) use ($examUserTable, $userTable) { + $join->on("$userTable.id", "=", "$examUserTable.uid") + ->on("$examUserTable.status", "=", DB::raw(ExamUser::STATUS_NORMAL)); + }) + ->whereRaw("$examUserTable.id is null") + ->selectRaw("$userTable.*") + ->chunk(100, function ($users) use ($exam) { + do_log("user count: " . $users->count() . last_query()); + $insert = []; + $now = Carbon::now()->toDateTimeString(); + foreach ($users as $user) { + $logPrefix = sprintf('[assignCronjob] user: %s, exam: %s', $user->id, $exam->id); + if (!$this->isExamMatchUser($exam, $user)) { + do_log("$logPrefix, exam not match user."); + continue; + } + $insert[] = [ + 'uid' => $user->id, + 'exam_id' => $exam->id, + 'created_at' => $now, + 'updated_at' => $now, + ]; + do_log("$logPrefix, exam assign to user."); + } + ExamUser::query()->insert($insert); + }); + return true; + } + + public function cronjobCheckout() + { + $now = Carbon::now()->toDateTimeString(); + $examUserTable = (new ExamUser())->getTable(); + $examTable = (new Exam())->getTable(); + $perPage = 100; + $page = 1; + $query = ExamUser::query() + ->join($examTable, "$examUserTable.exam_id", "=", "$examTable.id") + ->where("$examUserTable.status", ExamUser::STATUS_NORMAL) + ->whereRaw("if($examUserTable.begin is not null, $examUserTable.begin <= '$now', $examTable.begin <= '$now')") + ->whereRaw("if($examUserTable.end is not null, $examUserTable.end >= '$now', $examTable.end >= '$now')") + ->selectRaw("$examUserTable.*") + ->with(['exam', 'user', 'user.language']) + ->orderBy("$examUserTable.id", "asc"); + + while (true) { + $logPrefix = sprintf('[%s], page: %s', __FUNCTION__, $page); + $examUsers = $query->forPage($page, $perPage)->get("$examUserTable.*"); + if ($examUsers->isEmpty()) { + do_log("$logPrefix, no more data..." . last_query()); + break; + } else { + do_log("$logPrefix, fetch exam users: {$examUsers->count()}, " . last_query()); + } + $now = Carbon::now()->toDateTimeString(); + $idArr = $uidToDisable = $messageToSend = []; + foreach ($examUsers as $examUser) { + $idArr[] = $examUser->id; + $uid = $examUser->uid; + $currentLogPrefix = sprintf("$logPrefix, user: %s, exam: %s, examUser: %s", $uid, $examUser->exam_id, $examUser->id); + if ($examUser->is_done) { + do_log("$currentLogPrefix, [is_done]"); + $messageToSend[] = [ + 'receiver' => $uid, + 'added' => $now, + 'subject' => 'Exam passed!', + 'msg' => sprintf( + 'Congratulations! You have complete the exam: %s in time(%s ~ %s)!', + $examUser->exam->name, $examUser->begin ?? $examUser->exam->begin, $examUser->end ?? $examUser->exam->end + ), + ]; + } else { + do_log("$currentLogPrefix, [will be banned]"); + //ban user + $uidToDisable[] = $uid; + $messageToSend[] = [ + 'receiver' => $uid, + 'added' => $now, + 'subject' => 'Exam not passed! And your account is banned!', + 'msg' => sprintf( + 'You did not complete the exam: %s in time(%s ~ %s), so your account has been banned!', + $examUser->exam->name, $examUser->begin ?? $examUser->exam->begin, $examUser->end ?? $examUser->exam->end + ), + ]; + } + } + DB::transaction(function () use ($uidToDisable, $messageToSend, $idArr) { + ExamUser::query()->whereIn('id', $idArr)->update(['status' => ExamUser::STATUS_FINISHED]); + Message::query()->insert($messageToSend); + if (!empty($uidToDisable)) { + User::query()->whereIn('id', $uidToDisable)->update(['enabled' => User::ENABLED_NO]); + } + }); + $page++; + } + return true; + } + diff --git a/app/Repositories/MessageRepository.php b/app/Repositories/MessageRepository.php new file mode 100644 index 00000000..853fc26e --- /dev/null +++ b/app/Repositories/MessageRepository.php @@ -0,0 +1,43 @@ +getSortFieldAndType($params); + $query->orderBy($sortField, $sortType); + return $query->paginate(); + } + + public function store(array $params) + { + $model = Message::query()->create($params); + return $model; + } + + public function update(array $params, $id) + { + $model = Message::query()->findOrFail($id); + $model->update($params); + return $model; + } + + public function getDetail($id) + { + $model = Message::query()->findOrFail($id); + return $model; + } + + public function delete($id) + { + $model = Message::query()->findOrFail($id); + $result = $model->delete(); + return $result; + } +} diff --git a/composer.json b/composer.json index 4d651252..abbec6f7 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "fruitcake/laravel-cors": "^2.0", "guzzlehttp/guzzle": "~6.0", "imdbphp/imdbphp": "^6.4", + "laravel-lang/lang": "~7.0", "laravel/framework": "^8.12", "laravel/sanctum": "^2.10", "laravel/tinker": "^2.5", diff --git a/composer.lock b/composer.lock index 37eb7556..b552a1ad 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1e34544d001a98aace85e50adb9ef74e", + "content-hash": "b775c2bbc9ad07b2efffe1afb30de056", "packages": [ { "name": "asm89/stack-cors", @@ -934,6 +934,57 @@ "description": "Library for retrieving film and tv information from IMDb", "time": "2020-11-08T18:20:39+00:00" }, + { + "name": "laravel-lang/lang", + "version": "7.0.9", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/lang.git", + "reference": "679a65755db37b35acd36a5e0ca51e055815a00a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/679a65755db37b35acd36a5e0ca51e055815a00a", + "reference": "679a65755db37b35acd36a5e0ca51e055815a00a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*" + }, + "suggest": { + "andrey-helldar/laravel-lang-publisher": "Easy installation and update of translation files for your project", + "arcanedev/laravel-lang": "Translations manager and checker for Laravel 5", + "overtrue/laravel-lang": "Command to add languages in your project" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team" + } + ], + "description": "Languages for Laravel", + "keywords": [ + "lang", + "languages", + "laravel", + "lpm" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/lang/issues", + "source": "https://github.com/Laravel-Lang/lang/tree/7.0.9" + }, + "time": "2020-11-30T20:35:41+00:00" + }, { "name": "laravel/framework", "version": "v8.35.1", diff --git a/database/migrations/2021_04_19_062743_create_exam_progress_table.php b/database/migrations/2021_04_19_062743_create_exam_progress_table.php index a7321b41..dd091205 100644 --- a/database/migrations/2021_04_19_062743_create_exam_progress_table.php +++ b/database/migrations/2021_04_19_062743_create_exam_progress_table.php @@ -20,7 +20,7 @@ class CreateExamProgressTable extends Migration $table->integer('uid')->index(); $table->integer('torrent_id'); $table->integer('index'); - $table->integer('value'); + $table->bigInteger('value'); $table->timestamps(); $table->index('created_at'); }); diff --git a/database/migrations/2021_04_24_084104_create_exam_users_table.php b/database/migrations/2021_04_24_084104_create_exam_users_table.php index 2baa9e9e..9e75b1c5 100644 --- a/database/migrations/2021_04_24_084104_create_exam_users_table.php +++ b/database/migrations/2021_04_24_084104_create_exam_users_table.php @@ -21,6 +21,7 @@ class CreateExamUsersTable extends Migration $table->dateTime('begin')->nullable(); $table->dateTime('end')->nullable(); $table->text('progress')->nullable(); + $table->tinyInteger('is_done')->default(0); $table->timestamps(); }); } diff --git a/include/functions.php b/include/functions.php index 5b4b2f7a..f822dc8d 100644 --- a/include/functions.php +++ b/include/functions.php @@ -2012,7 +2012,7 @@ function mkprettytime($s) { } if ($t["day"]) - return $t["day"] . $lang_functions['text_day'] . sprintf("%02d:%02d:%02d", $t["hour"], $t["min"], $t["sec"]); + return $t["day"] . ($lang_functions['text_day'] ?? 'days') . sprintf("%02d:%02d:%02d", $t["hour"], $t["min"], $t["sec"]); if ($t["hour"]) return sprintf("%d:%02d:%02d", $t["hour"], $t["min"], $t["sec"]); // if ($t["min"]) diff --git a/include/globalfunctions.php b/include/globalfunctions.php index 216ef75a..61b55cc2 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -523,7 +523,7 @@ function last_query($all = false) return nexus_json_encode($query); } -function formatDatetime($datetime, $format = 'Y-m-d H:i:s') +function format_datetime($datetime, $format = 'Y-m-d H:i:s') { if ($datetime instanceof \Carbon\Carbon) { return $datetime->format($format); @@ -534,9 +534,23 @@ function formatDatetime($datetime, $format = 'Y-m-d H:i:s') return $datetime; } -function formatTtl($seconds) +function nexus_trans($key) { - if ($seconds < 60) { - return $seconds . "seconds"; + if (!IN_NEXUS) { + return trans($key); } + static $translations; + if (is_null($translations)) { + $lang = get_langfolder_cookie(); + $lang = \App\Http\Middleware\Locale::$languageMaps[$lang] ?? 'en'; + $dir = ROOT_PATH . 'resources/lang/' . $lang; + $files = glob($dir . '/*.php'); + foreach ($files as $file) { + $basename = basename($file); + $values = require $file; + $key = strstr($basename, '.php', true); + arr_set($translations, $key, $values); + } + } + return arr_get($translations, $key); } diff --git a/nexus/Exam/Exam.php b/nexus/Exam/Exam.php index efc92ee8..76a3d4df 100644 --- a/nexus/Exam/Exam.php +++ b/nexus/Exam/Exam.php @@ -2,50 +2,34 @@ namespace Nexus\Exam; +use App\Models\ExamUser; use App\Repositories\ExamRepository; -use App\Models\Exam as ExamModel; class Exam { public function render($uid) { - global $lang_functions; $examRep = new ExamRepository(); - $userExam = $examRep->getUserExamProgress($uid); + $userExam = $examRep->getUserExamProgress($uid, ExamUser::STATUS_NORMAL, ['exam']); if (empty($userExam)) { return ''; } $exam = $userExam->exam; $row = []; - $row[] = sprintf('%s:%s', $lang_functions['exam_name'], $exam->name); - $row[] = sprintf('%s:%s ~ %s', $lang_functions['exam_time_range'], $exam->begin, $exam->end); - foreach ($exam->indexes as $key => $index) { + $row[] = sprintf('%s:%s', nexus_trans('exam.name'), $exam->name); + $row[] = sprintf('%s:%s ~ %s', nexus_trans('exam.time_range'), $userExam->begin, $userExam->end); + foreach ($userExam->progress_formatted as $key => $index) { if (isset($index['checked']) && $index['checked']) { - $requireValue = $index['require_value']; - $currentValue = $userExam->progress[$index['index']] ?? 0; - $unit = ExamModel::$indexes[$index['index']]['unit'] ?? ''; $row[] = sprintf( - '%s:%s, %s:%s %s, %s:%s, %s:%s', - $lang_functions['exam_index'] . ($key + 1), $lang_functions['exam_index_' . $index['index']] ?? '', - $lang_functions['exam_require'], $requireValue, $unit, - $lang_functions['exam_progress_current'], $this->formatCurrentValue($index['index'], $currentValue), - $lang_functions['exam_progress_result'], - $currentValue >= $requireValue ? $lang_functions['exam_progress_result_pass_yes'] : $lang_functions['exam_progress_result_pass_no'] + '%s:%s, %s:%s, %s:%s, %s:%s', + nexus_trans('exam.index') . ($key + 1), nexus_trans('exam.index_text_' . $index['index']), + nexus_trans('exam.require_value'), $index['require_value_formatted'], + nexus_trans('exam.current_value'), $index['current_value_formatted'], + nexus_trans('exam.result'), + $index['passed'] ? nexus_trans('exam.result_pass') : nexus_trans('exam.result_not_pass') ); } } return nl2br(implode("\n", $row)); } - - private function formatCurrentValue($indexId, $value) - { - if ($indexId == ExamModel::INDEX_DOWNLOADED || $indexId == ExamModel::INDEX_UPLOADED) { - return mksize($value); - } - if ($indexId == ExamModel::INDEX_SEED_TIME_AVERAGE) { - return mkprettytime($value); - } - return $value; - - } } diff --git a/resources/lang/en/exam.php b/resources/lang/en/exam.php new file mode 100644 index 00000000..bfbdbbcb --- /dev/null +++ b/resources/lang/en/exam.php @@ -0,0 +1,16 @@ + 'Exam name', + 'index' => 'Exam index', + 'time_range' => 'Exam time', + 'index_text_' . \App\Models\Exam::INDEX_UPLOADED => 'Uploaded', + 'index_text_' . \App\Models\Exam::INDEX_SEED_TIME_AVERAGE => 'Seed time average', + 'index_text_' . \App\Models\Exam::INDEX_DOWNLOADED => 'Downloaded', + 'index_text_' . \App\Models\Exam::INDEX_BONUS => 'Bonus', + 'require_value' => 'Require', + 'current_value' => 'Current', + 'result' => 'Result', + 'result_pass' => 'Pass!', + 'result_not_pass' => 'Not pass!', +]; diff --git a/resources/lang/zh_CN/auth.php b/resources/lang/zh_CN/auth.php new file mode 100644 index 00000000..c95c4fa1 --- /dev/null +++ b/resources/lang/zh_CN/auth.php @@ -0,0 +1,17 @@ + '用户名或密码错误', + 'throttle' => '您尝试的登录次数过多,请 :seconds 秒后再试。', +]; diff --git a/resources/lang/zh_CN/exam.php b/resources/lang/zh_CN/exam.php new file mode 100644 index 00000000..13b90d17 --- /dev/null +++ b/resources/lang/zh_CN/exam.php @@ -0,0 +1,16 @@ + '考核名称', + 'index' => '考核指标', + 'time_range' => '考核时间', + 'index_text_' . \App\Models\Exam::INDEX_UPLOADED => '上传量', + 'index_text_' . \App\Models\Exam::INDEX_SEED_TIME_AVERAGE => '平均做种时间', + 'index_text_' . \App\Models\Exam::INDEX_DOWNLOADED => '下载量', + 'index_text_' . \App\Models\Exam::INDEX_BONUS => '魔力', + 'require_value' => '要求', + 'current_value' => '当前', + 'result' => '结果', + 'result_pass' => '通过!', + 'result_not_pass' => '未通过!', +]; diff --git a/resources/lang/zh_CN/pagination.php b/resources/lang/zh_CN/pagination.php new file mode 100644 index 00000000..a34b4554 --- /dev/null +++ b/resources/lang/zh_CN/pagination.php @@ -0,0 +1,17 @@ + '« 上一页', + 'next' => '下一页 »', +]; diff --git a/resources/lang/zh_CN/passwords.php b/resources/lang/zh_CN/passwords.php new file mode 100644 index 00000000..91c564cd --- /dev/null +++ b/resources/lang/zh_CN/passwords.php @@ -0,0 +1,20 @@ + '密码重置成功!', + 'sent' => '密码重置邮件已发送!', + 'throttled' => '请稍候再试。', + 'token' => '密码重置令牌无效。', + 'user' => '找不到该邮箱对应的用户。', +]; diff --git a/resources/lang/zh_CN/validation-inline.php b/resources/lang/zh_CN/validation-inline.php new file mode 100644 index 00000000..763e060a --- /dev/null +++ b/resources/lang/zh_CN/validation-inline.php @@ -0,0 +1,182 @@ + '您必须接受。', + 'active_url' => '不是一个有效的网址。', + 'after' => '必须要晚于 :date。', + 'after_or_equal' => '必须要等于 :date 或更晚。', + 'alpha' => '只能由字母组成。', + 'alpha_dash' => '只能由字母、数字、短划线(-)和下划线(_)组成。', + 'alpha_num' => '只能由字母和数字组成。', + 'array' => '必须是一个数组。', + 'before' => '必须要早于 :date。', + 'before_or_equal' => '必须要等于 :date 或更早。', + 'between' => [ + 'numeric' => '必须介于 :min - :max 之间。', + 'file' => '必须介于 :min - :max KB 之间。', + 'string' => '必须介于 :min - :max 个字符之间。', + 'array' => '必须只有 :min - :max 个单元。', + ], + 'boolean' => '必须为布尔值。', + 'confirmed' => '两次输入不一致。', + 'date' => '不是一个有效的日期。', + 'date_equals' => '必须要等于 :date。', + 'date_format' => '格式必须为 :format。', + 'different' => '必须和 :other 不同。', + 'digits' => '必须是 :digits 位的数字。', + 'digits_between' => '必须是介于 :min 和 :max 位的数字。', + 'dimensions' => '图片尺寸不正确。', + 'distinct' => '已经存在。', + 'email' => '不是一个合法的邮箱。', + 'ends_with' => '必须以 :values 为结尾。', + 'exists' => '不存在。', + 'file' => '必须是文件。', + 'filled' => '不能为空。', + 'gt' => [ + 'numeric' => '必须大于 :value。', + 'file' => '必须大于 :value KB。', + 'string' => '必须多于 :value 个字符。', + 'array' => '必须多于 :value 个元素。', + ], + 'gte' => [ + 'numeric' => '必须大于或等于 :value。', + 'file' => '必须大于或等于 :value KB。', + 'string' => '必须多于或等于 :value 个字符。', + 'array' => '必须多于或等于 :value 个元素。', + ], + 'image' => '必须是图片。', + 'in' => '选项无效。', + 'in_array' => '必须在 :other 中。', + 'integer' => '必须是整数。', + 'ip' => '必须是有效的 IP 地址。', + 'ipv4' => '必须是有效的 IPv4 地址。', + 'ipv6' => '必须是有效的 IPv6 地址。', + 'json' => '必须是正确的 JSON 格式。', + 'lt' => [ + 'numeric' => '必须小于 :value。', + 'file' => '必须小于 :value KB。', + 'string' => '必须少于 :value 个字符。', + 'array' => '必须少于 :value 个元素。', + ], + 'lte' => [ + 'numeric' => '必须小于或等于 :value。', + 'file' => '必须小于或等于 :value KB。', + 'string' => '必须少于或等于 :value 个字符。', + 'array' => '必须少于或等于 :value 个元素。', + ], + 'max' => [ + 'numeric' => '不能大于 :max。', + 'file' => '不能大于 :max KB。', + 'string' => '不能大于 :max 个字符。', + 'array' => '最多只有 :max 个单元。', + ], + 'mimes' => '必须是一个 :values 类型的文件。', + 'mimetypes' => '必须是一个 :values 类型的文件。', + 'min' => [ + 'numeric' => '必须大于等于 :min。', + 'file' => '大小不能小于 :min KB。', + 'string' => '至少为 :min 个字符。', + 'array' => '至少有 :min 个单元。', + ], + 'multiple_of' => 'The value must be a multiple of :value', + 'not_in' => '选项无效。', + 'not_regex' => '格式错误。', + 'numeric' => '必须是一个数字。', + 'password' => '密码错误', + 'present' => '必须存在。', + 'regex' => '格式不正确。', + 'required' => '不能为空。', + 'required_if' => '当 :other 为 :value 时不能为空。', + 'required_unless' => '当 :other 不为 :values 时不能为空。', + 'required_with' => '当 :values 存在时不能为空。', + 'required_with_all' => '当 :values 存在时不能为空。', + 'required_without' => '当 :values 不存在时不能为空。', + 'required_without_all' => '当 :values 都不存在时不能为空。', + 'same' => '必须和 :other 相同。', + 'size' => [ + 'numeric' => '大小必须为 :size。', + 'file' => '大小必须为 :size KB。', + 'string' => '必须是 :size 个字符。', + 'array' => '必须为 :size 个单元。', + ], + 'starts_with' => '必须以 :values 为开头。', + 'string' => '必须是一个字符串。', + 'timezone' => '必须是一个合法的时区值。', + 'unique' => '已经存在。', + 'uploaded' => '上传失败。', + 'url' => '格式不正确。', + 'uuid' => '必须是有效的 UUID。', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'name' => '名称', + 'username' => '用户名', + 'email' => '邮箱', + 'first_name' => '名', + 'last_name' => '姓', + 'password' => '密码', + 'password_confirmation' => '确认密码', + 'city' => '城市', + 'country' => '国家', + 'address' => '地址', + 'phone' => '电话', + 'mobile' => '手机', + 'age' => '年龄', + 'sex' => '性别', + 'gender' => '性别', + 'day' => '天', + 'month' => '月', + 'year' => '年', + 'hour' => '时', + 'minute' => '分', + 'second' => '秒', + 'title' => '标题', + 'content' => '内容', + 'description' => '描述', + 'excerpt' => '摘要', + 'date' => '日期', + 'time' => '时间', + 'available' => '可用的', + 'size' => '大小', + ], + +]; diff --git a/resources/lang/zh_CN/validation.php b/resources/lang/zh_CN/validation.php new file mode 100644 index 00000000..a5a27335 --- /dev/null +++ b/resources/lang/zh_CN/validation.php @@ -0,0 +1,180 @@ + '您必须接受 :attribute。', + 'active_url' => ':attribute 不是一个有效的网址。', + 'after' => ':attribute 必须要晚于 :date。', + 'after_or_equal' => ':attribute 必须要等于 :date 或更晚。', + 'alpha' => ':attribute 只能由字母组成。', + 'alpha_dash' => ':attribute 只能由字母、数字、短划线(-)和下划线(_)组成。', + 'alpha_num' => ':attribute 只能由字母和数字组成。', + 'array' => ':attribute 必须是一个数组。', + 'before' => ':attribute 必须要早于 :date。', + 'before_or_equal' => ':attribute 必须要等于 :date 或更早。', + 'between' => [ + 'numeric' => ':attribute 必须介于 :min - :max 之间。', + 'file' => ':attribute 必须介于 :min - :max KB 之间。', + 'string' => ':attribute 必须介于 :min - :max 个字符之间。', + 'array' => ':attribute 必须只有 :min - :max 个单元。', + ], + 'boolean' => ':attribute 必须为布尔值。', + 'confirmed' => ':attribute 两次输入不一致。', + 'date' => ':attribute 不是一个有效的日期。', + 'date_equals' => ':attribute 必须要等于 :date。', + 'date_format' => ':attribute 的格式必须为 :format。', + 'different' => ':attribute 和 :other 必须不同。', + 'digits' => ':attribute 必须是 :digits 位数字。', + 'digits_between' => ':attribute 必须是介于 :min 和 :max 位的数字。', + 'dimensions' => ':attribute 图片尺寸不正确。', + 'distinct' => ':attribute 已经存在。', + 'email' => ':attribute 不是一个合法的邮箱。', + 'ends_with' => ':attribute 必须以 :values 为结尾。', + 'exists' => ':attribute 不存在。', + 'file' => ':attribute 必须是文件。', + 'filled' => ':attribute 不能为空。', + 'gt' => [ + 'numeric' => ':attribute 必须大于 :value。', + 'file' => ':attribute 必须大于 :value KB。', + 'string' => ':attribute 必须多于 :value 个字符。', + 'array' => ':attribute 必须多于 :value 个元素。', + ], + 'gte' => [ + 'numeric' => ':attribute 必须大于或等于 :value。', + 'file' => ':attribute 必须大于或等于 :value KB。', + 'string' => ':attribute 必须多于或等于 :value 个字符。', + 'array' => ':attribute 必须多于或等于 :value 个元素。', + ], + 'image' => ':attribute 必须是图片。', + 'in' => '已选的属性 :attribute 无效。', + 'in_array' => ':attribute 必须在 :other 中。', + 'integer' => ':attribute 必须是整数。', + 'ip' => ':attribute 必须是有效的 IP 地址。', + 'ipv4' => ':attribute 必须是有效的 IPv4 地址。', + 'ipv6' => ':attribute 必须是有效的 IPv6 地址。', + 'json' => ':attribute 必须是正确的 JSON 格式。', + 'lt' => [ + 'numeric' => ':attribute 必须小于 :value。', + 'file' => ':attribute 必须小于 :value KB。', + 'string' => ':attribute 必须少于 :value 个字符。', + 'array' => ':attribute 必须少于 :value 个元素。', + ], + 'lte' => [ + 'numeric' => ':attribute 必须小于或等于 :value。', + 'file' => ':attribute 必须小于或等于 :value KB。', + 'string' => ':attribute 必须少于或等于 :value 个字符。', + 'array' => ':attribute 必须少于或等于 :value 个元素。', + ], + 'max' => [ + 'numeric' => ':attribute 不能大于 :max。', + 'file' => ':attribute 不能大于 :max KB。', + 'string' => ':attribute 不能大于 :max 个字符。', + 'array' => ':attribute 最多只有 :max 个单元。', + ], + 'mimes' => ':attribute 必须是一个 :values 类型的文件。', + 'mimetypes' => ':attribute 必须是一个 :values 类型的文件。', + 'min' => [ + 'numeric' => ':attribute 必须大于等于 :min。', + 'file' => ':attribute 大小不能小于 :min KB。', + 'string' => ':attribute 至少为 :min 个字符。', + 'array' => ':attribute 至少有 :min 个单元。', + ], + 'multiple_of' => 'The :attribute must be a multiple of :value', + 'not_in' => '已选的属性 :attribute 非法。', + 'not_regex' => ':attribute 的格式错误。', + 'numeric' => ':attribute 必须是一个数字。', + 'password' => '密码错误', + 'present' => ':attribute 必须存在。', + 'regex' => ':attribute 格式不正确。', + 'required' => ':attribute 不能为空。', + 'required_if' => '当 :other 为 :value 时 :attribute 不能为空。', + 'required_unless' => '当 :other 不为 :values 时 :attribute 不能为空。', + 'required_with' => '当 :values 存在时 :attribute 不能为空。', + 'required_with_all' => '当 :values 存在时 :attribute 不能为空。', + 'required_without' => '当 :values 不存在时 :attribute 不能为空。', + 'required_without_all' => '当 :values 都不存在时 :attribute 不能为空。', + 'same' => ':attribute 和 :other 必须相同。', + 'size' => [ + 'numeric' => ':attribute 大小必须为 :size。', + 'file' => ':attribute 大小必须为 :size KB。', + 'string' => ':attribute 必须是 :size 个字符。', + 'array' => ':attribute 必须为 :size 个单元。', + ], + 'starts_with' => ':attribute 必须以 :values 为开头。', + 'string' => ':attribute 必须是一个字符串。', + 'timezone' => ':attribute 必须是一个合法的时区值。', + 'unique' => ':attribute 已经存在。', + 'uploaded' => ':attribute 上传失败。', + 'url' => ':attribute 格式不正确。', + 'uuid' => ':attribute 必须是有效的 UUID。', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'name' => '名称', + 'username' => '用户名', + 'email' => '邮箱', + 'first_name' => '名', + 'last_name' => '姓', + 'password' => '密码', + 'password_confirmation' => '确认密码', + 'city' => '城市', + 'country' => '国家', + 'address' => '地址', + 'phone' => '电话', + 'mobile' => '手机', + 'age' => '年龄', + 'sex' => '性别', + 'gender' => '性别', + 'day' => '天', + 'month' => '月', + 'year' => '年', + 'hour' => '时', + 'minute' => '分', + 'second' => '秒', + 'title' => '标题', + 'content' => '内容', + 'description' => '描述', + 'excerpt' => '摘要', + 'date' => '日期', + 'time' => '时间', + 'available' => '可用的', + 'size' => '大小', + ], +]; diff --git a/resources/lang/zh_TW/auth.php b/resources/lang/zh_TW/auth.php new file mode 100644 index 00000000..9c8a37bf --- /dev/null +++ b/resources/lang/zh_TW/auth.php @@ -0,0 +1,17 @@ + '使用者名稱或密碼錯誤', + 'throttle' => '嘗試登入太多次,請在 :seconds 秒後再試。', +]; diff --git a/resources/lang/zh_TW/exam.php b/resources/lang/zh_TW/exam.php new file mode 100644 index 00000000..596c309a --- /dev/null +++ b/resources/lang/zh_TW/exam.php @@ -0,0 +1,16 @@ + '考核名稱', + 'index' => '考核指標', + 'time_range' => '考核時間', + 'index_text_' . \App\Models\Exam::INDEX_UPLOADED => '上傳量', + 'index_text_' . \App\Models\Exam::INDEX_SEED_TIME_AVERAGE => '平均做種時間', + 'index_text_' . \App\Models\Exam::INDEX_DOWNLOADED => '下載量', + 'index_text_' . \App\Models\Exam::INDEX_BONUS => '魔力', + 'require_value' => '要求', + 'current_value' => '當前', + 'result' => '結果', + 'result_pass' => '通過!', + 'result_not_pass' => '未通過!', +]; diff --git a/resources/lang/zh_TW/pagination.php b/resources/lang/zh_TW/pagination.php new file mode 100644 index 00000000..5b4d4046 --- /dev/null +++ b/resources/lang/zh_TW/pagination.php @@ -0,0 +1,17 @@ + '« 上一頁', + 'next' => '下一頁 »', +]; diff --git a/resources/lang/zh_TW/passwords.php b/resources/lang/zh_TW/passwords.php new file mode 100644 index 00000000..aa5c23a6 --- /dev/null +++ b/resources/lang/zh_TW/passwords.php @@ -0,0 +1,20 @@ + '密碼已成功重設!', + 'sent' => '密碼重設郵件已發送!', + 'throttled' => '請稍候再試。', + 'token' => '密碼重設碼無效。', + 'user' => '找不到該 E-mail 對應的使用者。', +]; diff --git a/resources/lang/zh_TW/validation-inline.php b/resources/lang/zh_TW/validation-inline.php new file mode 100644 index 00000000..f511fbfc --- /dev/null +++ b/resources/lang/zh_TW/validation-inline.php @@ -0,0 +1,181 @@ + '必須接受。', + 'active_url' => '不是有效的網址。', + 'after' => '必須要晚於 :date。', + 'after_or_equal' => '必須要等於 :date 或更晚。', + 'alpha' => '只能以字母組成。', + 'alpha_dash' => '只能以字母、數字、連接線(-)及底線(_)組成。', + 'alpha_num' => '只能以字母及數字組成。', + 'array' => '必須為陣列。', + 'before' => '必須要早於 :date。', + 'before_or_equal' => '必須要等於 :date 或更早。', + 'between' => [ + 'numeric' => '必須介於 :min 至 :max 之間。', + 'file' => '必須介於 :min 至 :max KB 之間。 ', + 'string' => '必須介於 :min 至 :max 個字元之間。', + 'array' => '必須有 :min - :max 個元素。', + ], + 'boolean' => '必須為布林值。', + 'confirmed' => '確認欄位的輸入不一致。', + 'date' => '不是有效的日期。', + 'date_equals' => '必須等於 :date。', + 'date_format' => '不符合 :format 的格式。', + 'different' => '必須與 :other 不同。', + 'digits' => '必須是 :digits 位數字。', + 'digits_between' => '必須介於 :min 至 :max 位數字。', + 'dimensions' => '圖片尺寸不正確。', + 'distinct' => '已經存在。', + 'email' => '必須是有效的 E-mail。', + 'ends_with' => '結尾必須包含下列之一::values。', + 'exists' => '不存在。', + 'file' => '必須是有效的檔案。', + 'filled' => '不能留空。', + 'gt' => [ + 'numeric' => '必須大於 :value。', + 'file' => '必須大於 :value KB。', + 'string' => '必須多於 :value 個字元。', + 'array' => '必須多於 :value 個元素。', + ], + 'gte' => [ + 'numeric' => '必須大於或等於 :value。', + 'file' => '必須大於或等於 :value KB。', + 'string' => '必須多於或等於 :value 個字元。', + 'array' => '必須多於或等於 :value 個元素。', + ], + 'image' => '必須是一張圖片。', + 'in' => '選項無效。', + 'in_array' => '沒有在 :other 中。', + 'integer' => '必須是一個整數。', + 'ip' => '必須是一個有效的 IP 位址。', + 'ipv4' => '必須是一個有效的 IPv4 位址。', + 'ipv6' => '必須是一個有效的 IPv6 位址。', + 'json' => '必須是正確的 JSON 字串。', + 'lt' => [ + 'numeric' => '必須小於 :value。', + 'file' => '必須小於 :value KB。', + 'string' => '必須少於 :value 個字元。', + 'array' => '必須少於 :value 個元素。', + ], + 'lte' => [ + 'numeric' => '必須小於或等於 :value。', + 'file' => '必須小於或等於 :value KB。', + 'string' => '必須少於或等於 :value 個字元。', + 'array' => '必須少於或等於 :value 個元素。', + ], + 'max' => [ + 'numeric' => '不能大於 :max。', + 'file' => '不能大於 :max KB。', + 'string' => '不能多於 :max 個字元。', + 'array' => '最多有 :max 個元素。', + ], + 'mimes' => '必須為 :values 的檔案。', + 'mimetypes' => '必須為 :values 的檔案。', + 'min' => [ + 'numeric' => '不能小於 :min。', + 'file' => '不能小於 :min KB。', + 'string' => '不能小於 :min 個字元。', + 'array' => '至少有 :min 個元素。', + ], + 'multiple_of' => 'The value must be a multiple of :value', + 'not_in' => '選項無效。', + 'not_regex' => '格式錯誤。', + 'numeric' => '必須為一個數字。', + 'password' => '密碼錯誤', + 'present' => '必須存在。', + 'regex' => '格式錯誤。', + 'required' => '不能留空。', + 'required_if' => '當 :other 是 :value 時不能留空。', + 'required_unless' => '當 :other 不是 :values 時不能留空。', + 'required_with' => '當 :values 出現時不能留空。', + 'required_with_all' => '當 :values 出現時不能為空。', + 'required_without' => '當 :values 留空時field 不能留空。', + 'required_without_all' => '當 :values 都不出現時不能留空。', + 'same' => '必須與 :other 相同。', + 'size' => [ + 'numeric' => '大小必須是 :size。', + 'file' => '大小必須是 :size KB。', + 'string' => '必須是 :size 個字元。', + 'array' => '必須是 :size 個元素。', + ], + 'starts_with' => '開頭必須包含下列之一::values。', + 'string' => '必須是一個字串。', + 'timezone' => '必須是一個正確的時區值。', + 'unique' => '已經存在。', + 'uploaded' => '上傳失敗。', + 'url' => '格式錯誤。', + 'uuid' => '必須是有效的 UUID。', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'address' => '地址', + 'age' => '年齡', + 'available' => '可用的', + 'city' => '城市', + 'content' => '內容', + 'country' => '國家', + 'date' => '日期', + 'day' => '天', + 'description' => '描述', + 'email' => 'E-mail', + 'excerpt' => '摘要', + 'first_name' => '名', + 'gender' => '性別', + 'hour' => '時', + 'last_name' => '姓', + 'minute' => '分', + 'mobile' => '手機', + 'month' => '月', + 'name' => '名稱', + 'password' => '密碼', + 'password_confirmation' => '確認密碼', + 'phone' => '電話', + 'second' => '秒', + 'sex' => '性別', + 'size' => '大小', + 'time' => '時間', + 'title' => '標題', + 'username' => '使用者名稱', + 'year' => '年', + ], + +]; diff --git a/resources/lang/zh_TW/validation.php b/resources/lang/zh_TW/validation.php new file mode 100644 index 00000000..70f75b2e --- /dev/null +++ b/resources/lang/zh_TW/validation.php @@ -0,0 +1,180 @@ + '必須接受 :attribute。', + 'active_url' => ':attribute 不是有效的網址。', + 'after' => ':attribute 必須要晚於 :date。', + 'after_or_equal' => ':attribute 必須要等於 :date 或更晚。', + 'alpha' => ':attribute 只能以字母組成。', + 'alpha_dash' => ':attribute 只能以字母、數字、連接線(-)及底線(_)組成。', + 'alpha_num' => ':attribute 只能以字母及數字組成。', + 'array' => ':attribute 必須為陣列。', + 'before' => ':attribute 必須要早於 :date。', + 'before_or_equal' => ':attribute 必須要等於 :date 或更早。', + 'between' => [ + 'numeric' => ':attribute 必須介於 :min 至 :max 之間。', + 'file' => ':attribute 必須介於 :min 至 :max KB 之間。 ', + 'string' => ':attribute 必須介於 :min 至 :max 個字元之間。', + 'array' => ':attribute: 必須有 :min - :max 個元素。', + ], + 'boolean' => ':attribute 必須為布林值。', + 'confirmed' => ':attribute 確認欄位的輸入不一致。', + 'date' => ':attribute 不是有效的日期。', + 'date_equals' => ':attribute 必須等於 :date。', + 'date_format' => ':attribute 不符合 :format 的格式。', + 'different' => ':attribute 與 :other 必須不同。', + 'digits' => ':attribute 必須是 :digits 位數字。', + 'digits_between' => ':attribute 必須介於 :min 至 :max 位數字。', + 'dimensions' => ':attribute 圖片尺寸不正確。', + 'distinct' => ':attribute 已經存在。', + 'email' => ':attribute 必須是有效的 E-mail。', + 'ends_with' => ':attribute 結尾必須包含下列之一::values。', + 'exists' => ':attribute 不存在。', + 'file' => ':attribute 必須是有效的檔案。', + 'filled' => ':attribute 不能留空。', + 'gt' => [ + 'numeric' => ':attribute 必須大於 :value。', + 'file' => ':attribute 必須大於 :value KB。', + 'string' => ':attribute 必須多於 :value 個字元。', + 'array' => ':attribute 必須多於 :value 個元素。', + ], + 'gte' => [ + 'numeric' => ':attribute 必須大於或等於 :value。', + 'file' => ':attribute 必須大於或等於 :value KB。', + 'string' => ':attribute 必須多於或等於 :value 個字元。', + 'array' => ':attribute 必須多於或等於 :value 個元素。', + ], + 'image' => ':attribute 必須是一張圖片。', + 'in' => '所選擇的 :attribute 選項無效。', + 'in_array' => ':attribute 沒有在 :other 中。', + 'integer' => ':attribute 必須是一個整數。', + 'ip' => ':attribute 必須是一個有效的 IP 位址。', + 'ipv4' => ':attribute 必須是一個有效的 IPv4 位址。', + 'ipv6' => ':attribute 必須是一個有效的 IPv6 位址。', + 'json' => ':attribute 必須是正確的 JSON 字串。', + 'lt' => [ + 'numeric' => ':attribute 必須小於 :value。', + 'file' => ':attribute 必須小於 :value KB。', + 'string' => ':attribute 必須少於 :value 個字元。', + 'array' => ':attribute 必須少於 :value 個元素。', + ], + 'lte' => [ + 'numeric' => ':attribute 必須小於或等於 :value。', + 'file' => ':attribute 必須小於或等於 :value KB。', + 'string' => ':attribute 必須少於或等於 :value 個字元。', + 'array' => ':attribute 必須少於或等於 :value 個元素。', + ], + 'max' => [ + 'numeric' => ':attribute 不能大於 :max。', + 'file' => ':attribute 不能大於 :max KB。', + 'string' => ':attribute 不能多於 :max 個字元。', + 'array' => ':attribute 最多有 :max 個元素。', + ], + 'mimes' => ':attribute 必須為 :values 的檔案。', + 'mimetypes' => ':attribute 必須為 :values 的檔案。', + 'min' => [ + 'numeric' => ':attribute 不能小於 :min。', + 'file' => ':attribute 不能小於 :min KB。', + 'string' => ':attribute 不能小於 :min 個字元。', + 'array' => ':attribute 至少有 :min 個元素。', + ], + 'multiple_of' => 'The :attribute must be a multiple of :value', + 'not_in' => '所選擇的 :attribute 選項無效。', + 'not_regex' => ':attribute 的格式錯誤。', + 'numeric' => ':attribute 必須為一個數字。', + 'password' => '密碼錯誤', + 'present' => ':attribute 必須存在。', + 'regex' => ':attribute 的格式錯誤。', + 'required' => ':attribute 不能留空。', + 'required_if' => '當 :other 是 :value 時 :attribute 不能留空。', + 'required_unless' => '當 :other 不是 :values 時 :attribute 不能留空。', + 'required_with' => '當 :values 出現時 :attribute 不能留空。', + 'required_with_all' => '當 :values 出現時 :attribute 不能為空。', + 'required_without' => '當 :values 留空時 :attribute field 不能留空。', + 'required_without_all' => '當 :values 都不出現時 :attribute 不能留空。', + 'same' => ':attribute 與 :other 必須相同。', + 'size' => [ + 'numeric' => ':attribute 的大小必須是 :size。', + 'file' => ':attribute 的大小必須是 :size KB。', + 'string' => ':attribute 必須是 :size 個字元。', + 'array' => ':attribute 必須是 :size 個元素。', + ], + 'starts_with' => ':attribute 開頭必須包含下列之一::values。', + 'string' => ':attribute 必須是一個字串。', + 'timezone' => ':attribute 必須是一個正確的時區值。', + 'unique' => ':attribute 已經存在。', + 'uploaded' => ':attribute 上傳失敗。', + 'url' => ':attribute 的格式錯誤。', + 'uuid' => ':attribute 必須是有效的 UUID。', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [ + 'address' => '地址', + 'age' => '年齡', + 'available' => '可用的', + 'city' => '城市', + 'content' => '內容', + 'country' => '國家', + 'date' => '日期', + 'day' => '天', + 'description' => '描述', + 'email' => 'E-mail', + 'excerpt' => '摘要', + 'first_name' => '名', + 'gender' => '性別', + 'hour' => '時', + 'last_name' => '姓', + 'minute' => '分', + 'mobile' => '手機', + 'month' => '月', + 'name' => '名稱', + 'password' => '密碼', + 'password_confirmation' => '確認密碼', + 'phone' => '電話', + 'second' => '秒', + 'sex' => '性別', + 'size' => '大小', + 'time' => '時間', + 'title' => '標題', + 'username' => '使用者名稱', + 'year' => '年', + ], +]; diff --git a/routes/api.php b/routes/api.php index df1a24f7..bfe92e1e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,7 +14,7 @@ use Illuminate\Support\Facades\Route; | */ -Route::group(['middleware' => ['auth:sanctum', 'permission']], function () { +Route::group(['middleware' => ['auth:sanctum', 'permission', 'locale']], function () { Route::post('logout', [\App\Http\Controllers\AuthenticateController::class, 'logout']); Route::resource('agent-allow', \App\Http\Controllers\AgentAllowController::class);