Skip to content

Commit

Permalink
[FEATURE] Add command to import glossary entries
Browse files Browse the repository at this point in the history
A common use case is to import glossary entries from a csv file. This command provides the feature to import glossary entries from a csv file, uploaded to the TYPO3 filelist. The entries are first imported to TYPO3 and then to deepl.
  • Loading branch information
BastiLu committed Oct 18, 2024
1 parent d82b991 commit bce3b00
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 11 deletions.
84 changes: 84 additions & 0 deletions Classes/Command/GlossaryCsvImportCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace WebVision\WvDeepltranslate\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
use WebVision\WvDeepltranslate\Service\ImportGlossaryEntryService;

class GlossaryCsvImportCommand extends Command
{
use GlossaryCommandTrait;

protected ImportGlossaryEntryService $importGlossaryEntryService;

public function __construct(ImportGlossaryEntryService $importGlossaryEntryService, ?string $name = null)
{
$this->importGlossaryEntryService = $importGlossaryEntryService;
parent::__construct($name);
}

protected function configure(): void
{
$this->addOption(
'pageId',
'p',
InputOption::VALUE_REQUIRED,
'Page to import to',
null
)
->addOption(
'csvFilePath',
'f',
InputOption::VALUE_REQUIRED,
'combined file identifier [[storage uid]:]<file identifier> e.g. 1:/user_upload/csv_imports',
null
)
->addOption(
'csvSeparator',
's',
InputOption::VALUE_OPTIONAL,
'csv seperator default: ,',
','
)
->addOption(
'targetSysLanguage',
't',
InputOption::VALUE_REQUIRED,
'The target language sys_language_uid',
null
);
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Glossary Entry CSV Import');

try {
$glossaryEntries = $this->importGlossaryEntryService->getGlossaryEntriesFromCsv(
(string) $input->getOption('csvFilePath'),
(string) $input->getOption('csvSeparator')
);

$this->importGlossaryEntryService->insertEntriesLocal(
$glossaryEntries,
(int) $input->getOption('pageId'),
(int) $input->getOption('targetSysLanguage')
);

$this->deeplGlossaryService->syncGlossaries((int)$input->getOption('pageId'));
} catch (Throwable $e) {
$io->error($e->getMessage());
return Command::FAILURE;
}

return Command::SUCCESS;
}
}
32 changes: 21 additions & 11 deletions Classes/Domain/Repository/GlossaryEntryRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@

namespace WebVision\WvDeepltranslate\Domain\Repository;

use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// @todo Consider to rename/move this as service class.
final class GlossaryEntryRepository
{
public const TABLE_NAME = 'tx_wvdeepltranslate_glossaryentry';

protected Connection $connection;

public function __construct(ConnectionPool $connectionPool)
{
$this->connection = $connectionPool->getConnectionForTable(self::TABLE_NAME);
}

/**
* @deprecated
*/
Expand All @@ -25,12 +34,9 @@ public function hasEntriesForGlossary(int $parentId): bool
*/
public function findEntriesByGlossary(int $parentId): array
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_wvdeepltranslate_glossaryentry');

$result = $connection->select(
$result = $this->connection->select(
['*'],
'tx_wvdeepltranslate_glossaryentry',
self::TABLE_NAME,
[
'glossary' => $parentId,
]
Expand All @@ -44,12 +50,9 @@ public function findEntriesByGlossary(int $parentId): array
*/
public function findEntryByUid(int $uid): array
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_wvdeepltranslate_glossaryentry');

$result = $connection->select(
$result = $this->connection->select(
['*'],
'tx_wvdeepltranslate_glossaryentry',
self::TABLE_NAME,
[
'uid' => $uid,
]
Expand All @@ -58,4 +61,11 @@ public function findEntryByUid(int $uid): array
// @todo Should we not better returning null instead of an empty array if nor recourd could be retrieved ?
return $result->fetchAssociative() ?: [];
}

public function add(array $entry): int
{
$this->connection->insert(self::TABLE_NAME, $entry);

return (int) $this->connection->lastInsertId(self::TABLE_NAME);
}
}
11 changes: 11 additions & 0 deletions Classes/Exception/FileNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace WebVision\WvDeepltranslate\Exception;

use Exception;

class FileNotFoundException extends Exception
{
}
70 changes: 70 additions & 0 deletions Classes/Service/ImportGlossaryEntryService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace WebVision\WvDeepltranslate\Service;

use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\CsvUtility;
use WebVision\WvDeepltranslate\Domain\Repository\GlossaryEntryRepository;
use WebVision\WvDeepltranslate\Exception\FileNotFoundException;

final class ImportGlossaryEntryService
{
protected ResourceFactory $resourceFactory;
protected GlossaryEntryRepository $glossaryEntryRepository;

public function __construct(ResourceFactory $resourceFactory, GlossaryEntryRepository $glossaryEntryRepository)
{
$this->resourceFactory = $resourceFactory;
$this->glossaryEntryRepository = $glossaryEntryRepository;
}

/**
* @throws FileNotFoundException
* @return array<mixed, array>
*/
public function getGlossaryEntriesFromCsv(string $filePath, string $separator): array
{
$file = $this->getFile($filePath);

if ($file === null) {
throw new FileNotFoundException('Csv file not found identifier: ' . $filePath, 1729245627);
}

return CsvUtility::csvToArray($file->getContents(), $separator, '"', 2);
}

/**
* @param array<mixed, array> $entries
* @param int $pageId
* @param int $sysLanguageUid
* @return void
*/
public function insertEntriesLocal(array $entries, int $pageId, int $sysLanguageUid): void
{
foreach ($entries as $entry) {
$originalUid = $this->glossaryEntryRepository->add([
'pid' => $pageId,
'term' => $entry[0],
]);

if ($originalUid > 0) {
$this->glossaryEntryRepository->add([
'l10n_parent' => $originalUid,
'sys_language_uid' => $sysLanguageUid,
'pid' => $pageId,
'term' => $entry[1],
]);
}
}
}

protected function getFile(string $csvFilePath): ?File
{
$file = $this->resourceFactory->getFileObjectFromCombinedIdentifier($csvFilePath);
return $file instanceof FileReference ? $file->getOriginalFile() : $file;
}
}
11 changes: 11 additions & 0 deletions Configuration/Services.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use WebVision\WvDeepltranslate\Client;
use WebVision\WvDeepltranslate\ClientInterface;
use WebVision\WvDeepltranslate\Command\GlossaryCleanupCommand;
use WebVision\WvDeepltranslate\Command\GlossaryCsvImportCommand;
use WebVision\WvDeepltranslate\Command\GlossaryListCommand;
use WebVision\WvDeepltranslate\Command\GlossarySyncCommand;
use WebVision\WvDeepltranslate\Controller\Backend\AjaxController;
Expand Down Expand Up @@ -77,6 +78,16 @@
'schedulable' => false,
]
);
$services
->set(GlossaryCsvImportCommand::class)
->tag(
'console.command',
[
'command' => 'deepl:glossary:import',
'description' => 'Import glossary entries from a csv file',
'schedulable' => true,
]
);

// add caching
$services->set('cache.wvdeepltranslate')
Expand Down

0 comments on commit bce3b00

Please sign in to comment.