diff --git a/app/Filament/Resources/System/TorrentStateResource.php b/app/Filament/Resources/System/TorrentStateResource.php
index c9acdbf3..0880216b 100644
--- a/app/Filament/Resources/System/TorrentStateResource.php
+++ b/app/Filament/Resources/System/TorrentStateResource.php
@@ -5,21 +5,18 @@ namespace App\Filament\Resources\System;
use Filament\Schemas\Schema;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\DateTimePicker;
+use Filament\Forms\Components\Textarea;
use Filament\Tables\Columns\TextColumn;
use Filament\Actions\EditAction;
+use Filament\Actions\DeleteAction;
+use Filament\Actions\DeleteBulkAction;
use App\Filament\Resources\System\TorrentStateResource\Pages\ManageTorrentStates;
-use App\Filament\Resources\System\TorrentStateResource\Pages;
-use App\Filament\Resources\System\TorrentStateResource\RelationManagers;
-use App\Models\Setting;
use App\Models\Torrent;
use App\Models\TorrentState;
-use Filament\Forms;
+use Carbon\Carbon;
use Filament\Resources\Resource;
use Filament\Tables\Table;
-use Filament\Tables;
use Illuminate\Database\Eloquent\Builder;
-use Illuminate\Database\Eloquent\SoftDeletingScope;
-use Nexus\Database\NexusDB;
class TorrentStateResource extends Resource
{
@@ -46,13 +43,28 @@ class TorrentStateResource extends Resource
return $schema
->components([
Select::make('global_sp_state')
- ->options(Torrent::listPromotionTypes(true))
+ ->options(function () {
+ $options = Torrent::listPromotionTypes(true);
+ unset($options[Torrent::PROMOTION_NORMAL]);
+ return $options;
+ })
->label(__('label.torrent_state.global_sp_state'))
->required(),
DateTimePicker::make('begin')
- ->label(__('label.begin')),
+ ->label(__('label.begin'))
+ ->required(),
DateTimePicker::make('deadline')
- ->label(__('label.deadline')),
+ ->label(__('label.deadline'))
+ ->required()
+ ->after('begin')
+ ->validationMessages([
+ 'after' => __('label.torrent_state.deadline_after_begin'),
+ ]),
+ Textarea::make('remark')
+ ->label(__('label.comment'))
+ ->rows(2)
+ ->columnSpanFull()
+ ->maxLength(255),
])->columns(1);
}
@@ -63,21 +75,63 @@ class TorrentStateResource extends Resource
TextColumn::make('global_sp_state_text')->label(__('label.torrent_state.global_sp_state')),
TextColumn::make('begin')->label(__('label.begin')),
TextColumn::make('deadline')->label(__('label.deadline')),
+ TextColumn::make('promotion_status')
+ ->label(__('label.torrent_state.status'))
+ ->state(function (TorrentState $record) {
+ $now = Carbon::now();
+ $begin = $record->begin ? Carbon::parse($record->begin) : null;
+ $deadline = $record->deadline ? Carbon::parse($record->deadline) : null;
+
+ if ($deadline && $deadline->lt($now)) {
+ return 'expired';
+ }
+ if ($begin && $begin->gt($now)) {
+ return 'upcoming';
+ }
+ return 'ongoing';
+ })
+ ->formatStateUsing(function (string $state) {
+ return match ($state) {
+ 'expired' => __('label.torrent_state.status_expired'),
+ 'upcoming' => __('label.torrent_state.status_upcoming'),
+ default => __('label.torrent_state.status_ongoing'),
+ };
+ })
+ ->badge()
+ ->sortable(query: function (Builder $query, string $direction): Builder {
+ $now = Carbon::now()->toDateTimeString();
+ // expired=0, ongoing=1, upcoming=2
+ return $query->orderByRaw(
+ "CASE
+ WHEN deadline IS NOT NULL AND deadline < ? THEN 0
+ WHEN begin IS NOT NULL AND begin > ? THEN 2
+ ELSE 1
+ END {$direction}",
+ [$now, $now]
+ );
+ })
+ ->color(fn (string $state) => match ($state) {
+ 'expired' => 'danger',
+ 'upcoming' => 'info',
+ default => 'success',
+ })
+ ->icon(fn (string $state) => match ($state) {
+ 'expired' => 'heroicon-o-x-circle',
+ 'upcoming' => 'heroicon-o-clock',
+ default => 'heroicon-o-check-circle',
+ })
+ ->iconPosition('before'),
+ TextColumn::make('remark')->label(__('label.comment'))->limit(50),
])
->filters([
//
])
->recordActions([
- EditAction::make()->after(function () {
- do_log("cache_del: global_promotion_state");
- NexusDB::cache_del(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
- do_log("publish_model_event: global_promotion_state_updated");
- publish_model_event("global_promotion_state_updated", 0);
- }),
-// Tables\Actions\DeleteAction::make(),
+ EditAction::make(),
+ DeleteAction::make(),
])
->toolbarActions([
-// Tables\Actions\DeleteBulkAction::make(),
+ DeleteBulkAction::make(),
]);
}
diff --git a/app/Filament/Resources/System/TorrentStateResource/Pages/ManageTorrentStates.php b/app/Filament/Resources/System/TorrentStateResource/Pages/ManageTorrentStates.php
index 8f2fa8fa..d16c634c 100644
--- a/app/Filament/Resources/System/TorrentStateResource/Pages/ManageTorrentStates.php
+++ b/app/Filament/Resources/System/TorrentStateResource/Pages/ManageTorrentStates.php
@@ -3,9 +3,8 @@
namespace App\Filament\Resources\System\TorrentStateResource\Pages;
use App\Filament\Resources\System\TorrentStateResource;
-use Filament\Pages\Actions;
+use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
-use Nexus\Database\NexusDB;
class ManageTorrentStates extends ManageRecords
{
@@ -14,7 +13,7 @@ class ManageTorrentStates extends ManageRecords
protected function getHeaderActions(): array
{
return [
-// Actions\CreateAction::make(),
+ CreateAction::make(),
];
}
diff --git a/app/Models/TorrentState.php b/app/Models/TorrentState.php
index d9b57447..47044764 100644
--- a/app/Models/TorrentState.php
+++ b/app/Models/TorrentState.php
@@ -4,17 +4,282 @@ namespace App\Models;
use App\Models\Traits\NexusActivityLogTrait;
+use App\Models\Setting;
+use Carbon\Carbon;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Facades\Request;
+use Illuminate\Validation\ValidationException;
+use Nexus\Database\NexusDB;
class TorrentState extends NexusModel
{
use NexusActivityLogTrait;
- protected $fillable = ['global_sp_state', 'deadline', 'begin'];
+ protected $fillable = ['global_sp_state', 'deadline', 'begin', 'remark'];
protected $table = 'torrents_state';
+ protected $casts = [
+ 'begin' => 'datetime',
+ 'deadline' => 'datetime',
+ ];
+
+ protected static function booted()
+ {
+ parent::booted();
+
+ static::saving(function (TorrentState $state) {
+ $state->validateTimeRange();
+ $state->ensureNoOverlap();
+ });
+
+ static::saved(function () {
+ static::flushCache();
+ });
+
+ static::deleted(function () {
+ static::flushCache();
+ });
+ }
+
public function getGlobalSpStateTextAttribute()
{
return Torrent::$promotionTypes[$this->global_sp_state]['text'] ?? '';
}
+
+ public function scopeActive(Builder $query, ?Carbon $moment = null): Builder
+ {
+ $moment = $moment ?? Carbon::now();
+
+ return $query
+ ->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
+ ->where(function (Builder $query) use ($moment) {
+ $query->whereNull('begin')->orWhere('begin', '<=', $moment);
+ })
+ ->where(function (Builder $query) use ($moment) {
+ $query->whereNull('deadline')->orWhere('deadline', '>=', $moment);
+ })
+ ->orderBy('begin')
+ ->orderBy('id');
+ }
+
+ public function scopeUpcoming(Builder $query, ?Carbon $moment = null): Builder
+ {
+ $moment = $moment ?? Carbon::now();
+
+ return $query
+ ->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
+ ->whereNotNull('begin')
+ ->where('begin', '>', $moment)
+ ->orderBy('begin')
+ ->orderBy('id');
+ }
+
+ public static function current(?Carbon $moment = null): ?self
+ {
+ return self::query()->active($moment)->first();
+ }
+
+ public static function next(?Carbon $moment = null): ?self
+ {
+ return self::query()->upcoming($moment)->first();
+ }
+
+ public static function cachedStates(): array
+ {
+ return NexusDB::remember(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY, 600, function () {
+ return self::query()
+ ->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
+ ->orderByRaw('begin is null')
+ ->orderBy('begin')
+ ->orderBy('id')
+ ->get()
+ ->toArray();
+ });
+ }
+
+ public static function flushCache(): void
+ {
+ do_log("cache_del: " . Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
+ NexusDB::cache_del(Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
+ do_log("publish_model_event: global_promotion_state_updated");
+ publish_model_event("global_promotion_state_updated", 0);
+ }
+
+ public static function resolveTimeline(?Carbon $moment = null): array
+ {
+ $moment = $moment ?? Carbon::now();
+ $states = self::cachedStates();
+ $current = null;
+ $upcoming = null;
+
+ foreach ($states as $state) {
+ $begin = self::parseDateTimeValue($state['begin'] ?? null);
+ $deadline = self::parseDateTimeValue($state['deadline'] ?? null);
+
+ $hasBegun = !$begin || $begin->lessThanOrEqualTo($moment);
+ $notExpired = !$deadline || $deadline->greaterThanOrEqualTo($moment);
+
+ if ($hasBegun && $notExpired) {
+ if (!$current) {
+ $current = $state;
+ }
+ continue;
+ }
+
+ if ($begin && $begin->greaterThan($moment)) {
+ if (!$upcoming) {
+ $upcoming = $state;
+ continue;
+ }
+ $upcomingBegin = self::parseDateTimeValue($upcoming['begin'] ?? null);
+ if ($upcomingBegin && $begin->lessThan($upcomingBegin)) {
+ $upcoming = $state;
+ }
+ }
+ }
+
+ return [
+ 'current' => $current,
+ 'upcoming' => $upcoming,
+ ];
+ }
+
+ protected function validateTimeRange(): void
+ {
+ $begin = self::parseDateTimeValue($this->begin);
+ $deadline = self::parseDateTimeValue($this->deadline);
+
+ if ($begin && $deadline && $deadline->lessThanOrEqualTo($begin)) {
+ throw ValidationException::withMessages([
+ self::errorFieldKey('deadline') => __('label.torrent_state.deadline_after_begin'),
+ ]);
+ }
+ }
+
+ protected function ensureNoOverlap(): void
+ {
+ self::validateNoOverlap($this->attributesToArray(), $this->id);
+ }
+
+ protected function getRangeForComparison(TorrentState $state): array
+ {
+ $min = Carbon::createFromTimestamp(0);
+ $max = Carbon::create(9999, 12, 31, 23, 59, 59);
+
+ $begin = self::parseDateTimeValue($state->begin) ?? $min;
+
+ $deadline = self::parseDateTimeValue($state->deadline) ?? $max;
+
+ return [
+ 'begin' => $begin,
+ 'end' => $deadline,
+ ];
+ }
+
+ protected static function parseDateTimeValue(mixed $value): ?Carbon
+ {
+ if ($value instanceof Carbon) {
+ return $value;
+ }
+
+ if (empty($value) || $value === '0000-00-00 00:00:00') {
+ return null;
+ }
+
+ return Carbon::parse($value);
+ }
+
+ public static function validateNoOverlap(array $attributes, ?int $ignoreId = null): void
+ {
+ $globalState = (int) Arr::get($attributes, 'global_sp_state', Torrent::PROMOTION_NORMAL);
+ if ($globalState === Torrent::PROMOTION_NORMAL) {
+ return;
+ }
+
+ $range = self::getRangeForArray($attributes);
+
+ $conflicts = self::query()
+ ->where('global_sp_state', '!=', Torrent::PROMOTION_NORMAL)
+ ->when($ignoreId, fn (Builder $query) => $query->whereKeyNot($ignoreId))
+ ->get(['id', 'begin', 'deadline']);
+
+ $beginConflict = $conflicts->first(function (TorrentState $state) use ($range) {
+ $other = $state->getRangeForComparison($state);
+ return $range['begin']->greaterThanOrEqualTo($other['begin']) && $range['begin']->lessThanOrEqualTo($other['end']);
+ });
+
+ $endConflict = $conflicts->first(function (TorrentState $state) use ($range) {
+ $other = $state->getRangeForComparison($state);
+ return $range['end']->greaterThanOrEqualTo($other['begin']) && $range['end']->lessThanOrEqualTo($other['end']);
+ });
+
+ $coverageConflict = $conflicts->first(function (TorrentState $state) use ($range) {
+ $other = $state->getRangeForComparison($state);
+ return $range['begin']->lt($other['begin']) && $range['end']->gt($other['end']);
+ });
+
+ if ($beginConflict || $endConflict || $coverageConflict) {
+ $errors = [];
+
+ if ($beginConflict) {
+ $errors[self::errorFieldKey('begin')] = self::buildOverlapMessage($beginConflict);
+ }
+
+ if ($endConflict) {
+ $errors[self::errorFieldKey('deadline')] = self::buildOverlapMessage($endConflict);
+ }
+
+ if (empty($errors) && $coverageConflict) {
+ $msg = self::buildOverlapMessage($coverageConflict);
+ $errors[self::errorFieldKey('begin')] = $msg;
+ $errors[self::errorFieldKey('deadline')] = $msg;
+ }
+
+ if (empty($errors)) {
+ $msg = __('label.torrent_state.time_overlaps');
+ $errors[self::errorFieldKey('begin')] = $msg;
+ $errors[self::errorFieldKey('deadline')] = $msg;
+ }
+
+ throw ValidationException::withMessages($errors);
+ }
+ }
+
+ protected static function getRangeForArray(array $attributes): array
+ {
+ $min = Carbon::createFromTimestamp(0);
+ $max = Carbon::create(9999, 12, 31, 23, 59, 59);
+
+ $begin = self::parseDateTimeValue($attributes['begin'] ?? null) ?? $min;
+ $deadline = self::parseDateTimeValue($attributes['deadline'] ?? null) ?? $max;
+
+ return [
+ 'begin' => $begin,
+ 'end' => $deadline,
+ ];
+ }
+
+ protected static function errorFieldKey(string $field): string
+ {
+ $prefix = 'mountedActions.0.data.';
+
+ return $prefix . $field;
+ }
+
+ protected static function buildOverlapMessage(TorrentState $conflict): string
+ {
+ $begin = self::parseDateTimeValue($conflict->begin);
+ $deadline = self::parseDateTimeValue($conflict->deadline);
+
+ $beginText = $begin ? $begin->toDateTimeString() : '-∞';
+ $deadlineText = $deadline ? $deadline->toDateTimeString() : '∞';
+
+ return __('label.torrent_state.time_overlaps_with', [
+ 'id' => $conflict->id,
+ 'begin' => $beginText,
+ 'end' => $deadlineText,
+ ]);
+ }
}
diff --git a/app/Policies/TorrentStatePolicy.php b/app/Policies/TorrentStatePolicy.php
index aeee0478..013117e2 100644
--- a/app/Policies/TorrentStatePolicy.php
+++ b/app/Policies/TorrentStatePolicy.php
@@ -42,7 +42,7 @@ class TorrentStatePolicy extends BasePolicy
*/
public function create(User $user)
{
- return false;
+ return $this->can($user);
}
/**
@@ -66,7 +66,7 @@ class TorrentStatePolicy extends BasePolicy
*/
public function delete(User $user, TorrentState $torrentState)
{
-
+ return $this->can($user);
}
/**
@@ -78,7 +78,7 @@ class TorrentStatePolicy extends BasePolicy
*/
public function restore(User $user, TorrentState $torrentState)
{
-
+ return $this->can($user);
}
/**
@@ -90,7 +90,7 @@ class TorrentStatePolicy extends BasePolicy
*/
public function forceDelete(User $user, TorrentState $torrentState)
{
- //
+ return $this->can($user);
}
private function can(User $user)
diff --git a/database/migrations/2024_08_26_000000_add_remark_to_torrents_state_table.php b/database/migrations/2024_08_26_000000_add_remark_to_torrents_state_table.php
new file mode 100644
index 00000000..44975ae6
--- /dev/null
+++ b/database/migrations/2024_08_26_000000_add_remark_to_torrents_state_table.php
@@ -0,0 +1,32 @@
+string('remark')->nullable()->after('deadline');
+ }
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('torrents_state', function (Blueprint $table) {
+ if (Schema::hasColumn('torrents_state', 'remark')) {
+ $table->dropColumn('remark');
+ }
+ });
+ }
+};
diff --git a/include/functions.php b/include/functions.php
index d43eec8e..fd395a6a 100644
--- a/include/functions.php
+++ b/include/functions.php
@@ -2823,15 +2823,34 @@ print '
';
}
if ($msgalert)
{
- $spStateGlobal = get_global_sp_state();
- if ($spStateGlobal != \App\Models\Torrent::PROMOTION_NORMAL) {
- $torrentGlobalStateRow = \Nexus\Database\NexusDB::cache_get(\App\Models\Setting::TORRENT_GLOBAL_STATE_CACHE_KEY);
- $msg = sprintf($lang_functions['full_site_promotion_in_effect'], \App\Models\Torrent::$promotionTypes[$spStateGlobal]['text']);
- if (!empty($torrentGlobalStateRow['begin']) || !empty($torrentGlobalStateRow['deadline'])) {
- $timeRange = sprintf($lang_functions['full_site_promotion_time_range'], $torrentGlobalStateRow['begin'] ?? '-∞', $torrentGlobalStateRow['deadline'] ?? '∞');
- $msg .= $timeRange;
+ $timeline = \App\Models\TorrentState::resolveTimeline();
+ $currentPromotion = $timeline['current'] ?? null;
+ $upcomingPromotion = $timeline['upcoming'] ?? null;
+ $remarkTpl = $lang_functions['full_site_promotion_remark'] ?? 'Remark: %s';
+
+ if ($currentPromotion) {
+ $promotionText = \App\Models\Torrent::$promotionTypes[$currentPromotion['global_sp_state']]['text'] ?? '';
+ $msg = sprintf($lang_functions['full_site_promotion_in_effect'], $promotionText);
+ if (!empty($currentPromotion['begin']) || !empty($currentPromotion['deadline'])) {
+ $timeRange = sprintf($lang_functions['full_site_promotion_time_range'], $currentPromotion['begin'] ?? '-∞', $currentPromotion['deadline'] ?? '∞');
+ $msg .= '
' . $timeRange;
+ }
+ if (!empty($currentPromotion['remark'])) {
+ $msg .= '
' . sprintf($remarkTpl, $currentPromotion['remark']);
}
msgalert("torrents.php", $msg, "green");
+ }
+ if ($upcomingPromotion) {
+ $promotionText = \App\Models\Torrent::$promotionTypes[$upcomingPromotion['global_sp_state']]['text'] ?? '';
+ $msg = sprintf($lang_functions['full_site_promotion_upcoming'] ?? 'Upcoming full site [%s]', $promotionText);
+ if (!empty($upcomingPromotion['begin']) || !empty($upcomingPromotion['deadline'])) {
+ $timeRange = sprintf($lang_functions['full_site_promotion_time_range'], $upcomingPromotion['begin'] ?? '-∞', $upcomingPromotion['deadline'] ?? '∞');
+ $msg .= '
' . $timeRange;
+ }
+ if (!empty($upcomingPromotion['remark'])) {
+ $msg .= '
' . sprintf($remarkTpl, $upcomingPromotion['remark']);
+ }
+ msgalert("torrents.php", $msg, "blue");
}
if($CURUSER['leechwarn'] == 'yes')
{
@@ -5994,7 +6013,7 @@ function get_ip_location_from_geoip($ip): bool|array
function msgalert($url, $text, $bgcolor = "red")
{
- print("
\n");
+ print("| \n");
if (!empty($url)) {
print("".$text."");
} else {
diff --git a/include/globalfunctions.php b/include/globalfunctions.php
index 36754299..57037c84 100644
--- a/include/globalfunctions.php
+++ b/include/globalfunctions.php
@@ -3,21 +3,14 @@
function get_global_sp_state()
{
static $global_promotion_state;
- $cacheKey = \App\Models\Setting::TORRENT_GLOBAL_STATE_CACHE_KEY;
if (is_null($global_promotion_state)) {
- $row = \Nexus\Database\NexusDB::remember($cacheKey, 600, function () use ($cacheKey) {
- return \Nexus\Database\NexusDB::getOne('torrents_state', 1);
- });
- if (is_array($row) && isset($row['deadline']) && $row['deadline'] < date('Y-m-d H:i:s')) {
- //expired
- $global_promotion_state = \App\Models\Torrent::PROMOTION_NORMAL;
- } elseif (is_array($row) && isset($row['begin']) && $row['begin'] > date('Y-m-d H:i:s')) {
- //Not begin
- $global_promotion_state = \App\Models\Torrent::PROMOTION_NORMAL;
- } elseif (is_array($row)) {
- $global_promotion_state = $row["global_sp_state"];
+ $timeline = \App\Models\TorrentState::resolveTimeline();
+ $current = $timeline['current'] ?? null;
+
+ if (is_array($current) && isset($current['global_sp_state'])) {
+ $global_promotion_state = $current['global_sp_state'];
} else {
- $global_promotion_state = $row;
+ $global_promotion_state = \App\Models\Torrent::PROMOTION_NORMAL;
}
}
return $global_promotion_state;
diff --git a/lang/chs/lang_functions.php b/lang/chs/lang_functions.php
index ab9236ac..3221d2b3 100644
--- a/lang/chs/lang_functions.php
+++ b/lang/chs/lang_functions.php
@@ -327,6 +327,8 @@ $lang_functions = array
'text_contactstaff' => '联系管理组',
'full_site_promotion_in_effect' => '全站 [%s] 生效中!',
'full_site_promotion_time_range' => '时间:%s ~ %s',
+ 'full_site_promotion_remark' => '备注:%s',
+ 'full_site_promotion_upcoming' => '即将生效的全站 [%s]',
'text_torrent_to_approval' => '有 %s%u 个待审核的种子%s',
'std_confirm_remove' => '确定要删除吗?',
'select_an_user_class' => '选择一个用户等级',
diff --git a/lang/cht/lang_functions.php b/lang/cht/lang_functions.php
index 058fa452..1f038eff 100644
--- a/lang/cht/lang_functions.php
+++ b/lang/cht/lang_functions.php
@@ -334,6 +334,8 @@ $lang_functions = array
'text_contactstaff' => '聯系管理組',
'full_site_promotion_in_effect' => '全站 [%s] 生效中!',
'full_site_promotion_time_range' => '時間:%s ~ %s',
+ 'full_site_promotion_remark' => '備註:%s',
+ 'full_site_promotion_upcoming' => '即將生效的全站 [%s]',
'text_torrent_to_approval' => '有 %s%u 個待審核的種子%s',
'std_confirm_remove' => '確定要刪除嗎?',
'select_an_user_class' => '選擇一個用戶等級',
diff --git a/lang/en/lang_functions.php b/lang/en/lang_functions.php
index c75b564a..a9b92907 100644
--- a/lang/en/lang_functions.php
+++ b/lang/en/lang_functions.php
@@ -335,6 +335,8 @@ $lang_functions = array
'text_contactstaff' => 'Contact staff',
'full_site_promotion_in_effect' => 'Full site [%s] in effect!',
'full_site_promotion_time_range' => 'Time range: %s ~ %s',
+ 'full_site_promotion_remark' => 'Note: %s',
+ 'full_site_promotion_upcoming' => 'Upcoming full site [%s]',
'text_torrent_to_approval' => 'There %s%u not approval torrent%s.',
'std_confirm_remove' => 'Are you sure you want to delete it?',
'select_an_user_class' => 'Select an user class',
diff --git a/resources/lang/en/label.php b/resources/lang/en/label.php
index e33091f4..0369ac8c 100644
--- a/resources/lang/en/label.php
+++ b/resources/lang/en/label.php
@@ -328,6 +328,13 @@ return [
'torrent_state' => [
'label' => 'Global promotion',
'global_sp_state' => 'Global promotion state',
+ 'deadline_after_begin' => 'End time must be later than start time.',
+ 'status' => 'Status',
+ 'status_expired' => 'Expired',
+ 'status_ongoing' => 'In progress',
+ 'status_upcoming' => 'Upcoming',
+ 'time_overlaps' => 'Time overlaps with another promotion. Please adjust the window.',
+ 'time_overlaps_with' => 'Overlaps with promotion ID :id (time: :begin ~ :end).',
],
'role' => [
'class' => 'Relate user class',
diff --git a/resources/lang/zh_CN/label.php b/resources/lang/zh_CN/label.php
index 34a81cf4..c9eb84cf 100644
--- a/resources/lang/zh_CN/label.php
+++ b/resources/lang/zh_CN/label.php
@@ -370,6 +370,13 @@ return [
'torrent_state' => [
'label' => '全站优惠',
'global_sp_state' => '全站优惠',
+ 'deadline_after_begin' => '结束时间必须晚于开始时间。',
+ 'status' => '状态',
+ 'status_expired' => '已过期',
+ 'status_ongoing' => '进行中',
+ 'status_upcoming' => '未开始',
+ 'time_overlaps' => '时间与已有活动重叠,请调整时间段。',
+ 'time_overlaps_with' => '与活动 ID :id (时间::begin ~ :end)重叠,请调整时间段。',
],
'role' => [
'class' => '关联用户等级',
diff --git a/resources/lang/zh_TW/label.php b/resources/lang/zh_TW/label.php
index da6f3699..0cb57b39 100644
--- a/resources/lang/zh_TW/label.php
+++ b/resources/lang/zh_TW/label.php
@@ -327,6 +327,13 @@ return [
'torrent_state' => [
'label' => '全站優惠',
'global_sp_state' => '全站優惠',
+ 'deadline_after_begin' => '結束時間必須晚於開始時間。',
+ 'status' => '狀態',
+ 'status_expired' => '已過期',
+ 'status_ongoing' => '進行中',
+ 'status_upcoming' => '未開始',
+ 'time_overlaps' => '時間與已有活動重疊,請調整時間段。',
+ 'time_overlaps_with' => '與活動 ID :id (時間::begin ~ :end)重疊,請調整時間段。',
],
'role' => [
'class' => '關聯用户等級',
|
|