self::interval(), 'display' => __( 'Aipogeo Context Translate background translation', 'aipogeo-context-translate' ), ); return $schedules; } /** * 让计划任务与设置保持一致:该开的开、该关的关。 * * 每个请求都会走一遍,但只是读一次已自动加载的 cron 选项,开销可以忽略。 * 间隔改了也不必重排:下次执行完核心按周期名重新取间隔,自然跟上。 * * @return void */ public static function sync() { $want = self::MODE_CRON === self::mode() && '' !== APGT_Settings::default_language() && APGT_Settings::target_languages(); $next = wp_next_scheduled( self::HOOK ); if ( $want && ! $next ) { wp_schedule_event( time() + 30, self::SCHEDULE, self::HOOK ); } elseif ( ! $want && $next ) { self::clear(); } } /** * 撤掉计划任务(停用插件、换成别的方式时)。 * * @return void */ public static function clear() { wp_clear_scheduled_hook( self::HOOK ); } /** * 计划任务本体:领一个待办页面翻译。 * * @return void */ public static function run() { if ( self::MODE_CRON !== self::mode() ) { return; } if ( '' === APGT_Settings::default_language() || ! APGT_Settings::target_languages() ) { return; } $task = APGT_Pages::next_task(); if ( ! $task ) { self::remember( 'idle', __( 'No pending pages', 'aipogeo-context-translate' ) ); return; } // 这一页正被别人翻(手动翻译或实时翻译),本次让出,下次再领。 $key = APGT_Lock::key( $task['url'], $task['language_to'] ); $owner = APGT_Lock::make_owner(); if ( ! APGT_Lock::acquire( $key, $owner ) ) { self::remember( 'busy', __( 'The next page is already being translated elsewhere', 'aipogeo-context-translate' ), $task ); return; } APGT_Translator::prepare_long_task(); APGT_Lock::keep_alive( $key, $owner ); try { $result = APGT_Translator::translate_page( APGT_Pages::to_absolute( $task['url'] ), $task['language_to'] ); // 一条都没译成也算失败:多半是 AI 配置或额度的问题,要让人看得出来。 $failed = empty( $result['ok'] ) || ( empty( $result['saved'] ) && ! empty( $result['failed'] ) ); $state = $failed ? 'failed' : 'done'; $text = (string) $result['message']; if ( ! empty( $result['errors'] ) ) { // 每一批报的往往是同一个错,去重后再拼。 $text .= ' ' . implode( '; ', array_unique( array_map( 'strval', (array) $result['errors'] ) ) ); } self::remember( $state, $text, $task ); } catch ( \Throwable $e ) { self::remember( 'failed', sprintf( /* translators: %s: error message */ __( 'Translation aborted unexpectedly: %s', 'aipogeo-context-translate' ), $e->getMessage() ), $task ); } APGT_Lock::release( $key, $owner ); } /** * 记下最近一次执行的结果,供设置页显示。 * * @param string $state done|failed|busy|idle。 * @param string $message 说明。 * @param array|null $task 处理的页面。 * @return void */ private static function remember( $state, $message, $task = null ) { update_option( self::OPT_LAST, array( 'time' => time(), 'state' => (string) $state, 'message' => wp_strip_all_tags( (string) $message ), 'url' => $task ? (string) $task['url'] : '', 'label' => $task ? (string) $task['label'] : '', 'language' => $task ? (string) $task['language_to'] : '', ), false ); } /** * 最近一次执行结果。 * * @return array|null */ public static function last() { $last = get_option( self::OPT_LAST ); return is_array( $last ) ? $last : null; } }