protected function extract_strings() {
		$translations = new Translations();

		// Add existing strings first but don't keep headers.
		if ( ! empty( $this->merge ) ) {
			$existing_translations = new Translations();
			Po::fromFile( $this->merge, $existing_translations );
			$translations->mergeWith( $existing_translations, Merge::ADD | Merge::REMOVE );
		}

		PotGenerator::setCommentBeforeHeaders( $this->get_file_comment() );

		$this->set_default_headers( $translations );

		// POT files have no Language header.
		$translations->deleteHeader( Translations::HEADER_LANGUAGE );

		// Only relevant for PO files, not POT files.
		$translations->setHeader( 'PO-Revision-Date', 'YEAR-MO-DA HO:MI+ZONE' );

		if ( $this->domain ) {
			$translations->setDomain( $this->domain );
		}

		unset( $this->main_file_data['Version'], $this->main_file_data['License'], $this->main_file_data['Domain Path'], $this->main_file_data['Text Domain'] );

		// Set entries from main file data.
		foreach ( $this->main_file_data as $header => $data ) {
			if ( empty( $data ) ) {
				continue;
			}

			$translation = new Translation( '', $data );

			if ( isset( $this->main_file_data['Theme Name'] ) ) {
				$translation->addExtractedComment( sprintf( '%s of the theme', $header ) );
			} else {
				$translation->addExtractedComment( sprintf( '%s of the plugin', $header ) );
			}

			$translations[] = $translation;
		}

		try {
			$options = [
				// Extract 'Template Name' headers in theme files.
				'wpExtractTemplates' => isset( $this->main_file_data['Theme Name'] ),
				'include'            => $this->include,
				'exclude'            => $this->exclude,
				'extensions'         => [ 'php' ],
			];
			PhpCodeExtractor::fromDirectory( $this->source, $translations, $options );

			if ( ! $this->skip_js ) {
				JsCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'js' ],
					]
				);

				MapCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'map' ],
					]
				);
			}
		} catch ( \Exception $e ) {
			WP_CLI::error( $e->getMessage() );
		}

		foreach ( $this->exceptions as $translation ) {
			if ( $translations->find( $translation ) ) {
				unset( $translations[ $translation->getId() ] );
			}
		}

		if ( ! $this->skip_audit ) {
			$this->audit_strings( $translations );
		}

		return $translations;
	}