.. include:: /Includes.rst.txt .. _developer: ================== Developer Manual ================== .. _dev-overview: Overview ======== This section covers integration of the TextDB extension into your TYPO3 project, including ViewHelpers, APIs, services, and extension points. .. _dev-architecture: Architecture ============ Domain Model ------------ The extension uses Extbase domain-driven design: .. code-block:: none Domain Models: ├── Translation # Main translation record ├── Component # Logical grouping (website, shop, etc.) ├── Type # Category (label, message, error) └── Environment # Context (dev, staging, production) Repositories: ├── TranslationRepository ├── ComponentRepository ├── TypeRepository └── EnvironmentRepository Services: ├── TranslationService # Core translation logic └── ImportService # XLIFF import handling Controllers: └── TranslationController # Backend module Dependency Injection -------------------- All services use constructor injection via `Configuration/Services.yaml`: .. code-block:: yaml services: _defaults: autowire: true autoconfigure: true public: false Netresearch\NrTextdb\: resource: '../Classes/*' exclude: '../Classes/Domain/Model/*' .. _dev-viewhelpers: ViewHelpers =========== TextDB ViewHelper ----------------- Main ViewHelper for displaying translations in Fluid templates. **Namespace:** .. code-block:: html {namespace textdb=Netresearch\NrTextdb\ViewHelpers} **Usage:** .. code-block:: html **Parameters:** :`component`: (required) Component identifier :`type`: (required) Type identifier (label, message, etc.) :`placeholder`: (required) Translation key :`default`: (optional) Fallback text if translation not found **Output:** Returns the translated text for the current page language. **Example:** .. code-block:: html

{textdb:textdb(component: 'website', type: 'label', placeholder: 'button.submit')} Translate ViewHelper -------------------- Alternative ViewHelper compatible with f:translate interface. **Usage:** .. code-block:: html {namespace textdb=Netresearch\NrTextdb\ViewHelpers} **Component Configuration:** Set component in controller: .. code-block:: php use Netresearch\NrTextdb\ViewHelpers\TranslateViewHelper; class MyController extends ActionController { public function initializeAction(): void { TranslateViewHelper::$component = 'my-component'; } } **Auto-Import Feature:** When enabled (`createIfMissing = 1`), this ViewHelper will: 1. Load translation from XLIFF file on first request 2. Create TextDB record automatically 3. Use TextDB record on subsequent requests .. _dev-services: Services API ============ TranslationService ------------------ Core service for translation management. **Injection:** .. code-block:: php use Netresearch\NrTextdb\Service\TranslationService; class MyClass { public function __construct( private readonly TranslationService $translationService ) {} } **Methods:** getTranslation() ~~~~~~~~~~~~~~~~ .. code-block:: php public function getTranslation( string $component, string $type, string $placeholder, int $languageUid = 0 ): ?Translation Get translation record. **Example:** .. code-block:: php $translation = $this->translationService->getTranslation( component: 'website', type: 'label', placeholder: 'welcome.message', languageUid: 1 ); if ($translation) { echo $translation->getValue(); } createTranslation() ~~~~~~~~~~~~~~~~~~~ .. code-block:: php public function createTranslation( string $component, string $type, string $placeholder, string $value, int $languageUid = 0 ): Translation Create new translation record. **Example:** .. code-block:: php $translation = $this->translationService->createTranslation( component: 'shop', type: 'label', placeholder: 'cart.add', value: 'Add to cart', languageUid: 0 ); ImportService ------------- Service for importing XLIFF files. **Injection:** .. code-block:: php use Netresearch\NrTextdb\Service\ImportService; public function __construct( private readonly ImportService $importService ) {} **Methods:** importXliffFile() ~~~~~~~~~~~~~~~~~ .. code-block:: php public function importXliffFile( string $filePath, bool $overwriteExisting = false ): array Import translations from XLIFF file. **Returns:** Array with import statistics .. code-block:: php [ 'imported' => 150, 'updated' => 25, 'skipped' => 10, 'errors' => [] ] **Example:** .. code-block:: php $result = $this->importService->importXliffFile( filePath: '/path/to/translations.xlf', overwriteExisting: true ); echo "Imported: {$result['imported']} translations"; .. _dev-repositories: Repositories ============ TranslationRepository --------------------- Repository for translation records. **Custom Methods:** findByComponentAndType() ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: php public function findByComponentAndType( Component $component, Type $type ): QueryResultInterface Find all translations for component and type. findByPlaceholder() ~~~~~~~~~~~~~~~~~~~ .. code-block:: php public function findByPlaceholder( string $placeholder, int $languageUid = 0 ): ?Translation Find translation by placeholder key. **Example:** .. code-block:: php use Netresearch\NrTextdb\Domain\Repository\TranslationRepository; public function __construct( private readonly TranslationRepository $repository ) {} public function myAction(): void { $translations = $this->repository->findByComponentAndType( $component, $type ); foreach ($translations as $translation) { // Process translations } } .. _dev-domain-models: Domain Models ============= Translation Model ----------------- Main translation entity. **Properties:** .. code-block:: php class Translation extends AbstractEntity { protected string $placeholder = ''; protected string $value = ''; protected ?Component $component = null; protected ?Type $type = null; protected ?Environment $environment = null; protected int $sysLanguageUid = 0; } **Getters/Setters:** .. code-block:: php $translation = new Translation(); $translation->setPlaceholder('welcome.message'); $translation->setValue('Welcome!'); $translation->setComponent($component); $translation->setType($type); echo $translation->getValue(); // "Welcome!" Component Model --------------- .. code-block:: php class Component extends AbstractEntity { protected string $name = ''; protected string $identifier = ''; } Type Model ---------- .. code-block:: php class Type extends AbstractEntity { protected string $name = ''; protected string $identifier = ''; } Environment Model ----------------- .. code-block:: php class Environment extends AbstractEntity { protected string $name = ''; protected string $identifier = ''; } .. _dev-commands: Console Commands ================ ImportCommand ------------- CLI command for importing translations. **Location:** `Classes/Command/ImportCommand.php` **Usage:** .. code-block:: bash vendor/bin/typo3 nr_textdb:import [file-path] **Configuration:** .. code-block:: yaml # Configuration/Services.yaml Netresearch\NrTextdb\Command\ImportCommand: tags: - name: 'console.command' command: 'nr_textdb:import' description: 'Imports textdb records from language files' schedulable: false **Creating Custom Commands:** .. code-block:: php namespace MyVendor\MyExt\Command; use Netresearch\NrTextdb\Service\ImportService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class CustomImportCommand extends Command { public function __construct( private readonly ImportService $importService ) { parent::__construct(); } protected function execute( InputInterface $input, OutputInterface $output ): int { $files = glob('/path/to/translations/*.xlf'); foreach ($files as $file) { $result = $this->importService->importXliffFile($file); $output->writeln("Imported {$result['imported']} from {$file}"); } return Command::SUCCESS; } } .. _dev-events: Events & Hooks ============== The extension currently uses standard Extbase/TYPO3 patterns. Future versions may add PSR-14 events for extensibility. **Potential Event Points:** * Before/After translation import * Before/After translation creation * Translation retrieval (for caching) * Export generation .. _dev-api-examples: API Examples ============ Example 1: Programmatic Translation Management ----------------------------------------------- .. code-block:: php use Netresearch\NrTextdb\Domain\Model\Translation; use Netresearch\NrTextdb\Domain\Repository\TranslationRepository; use Netresearch\NrTextdb\Domain\Repository\ComponentRepository; use Netresearch\NrTextdb\Domain\Repository\TypeRepository; use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; class TranslationManager { public function __construct( private readonly TranslationRepository $translationRepository, private readonly ComponentRepository $componentRepository, private readonly TypeRepository $typeRepository, private readonly PersistenceManager $persistenceManager ) {} public function createBulkTranslations(array $data): void { $component = $this->componentRepository->findByIdentifier('website'); $type = $this->typeRepository->findByIdentifier('label'); foreach ($data as $key => $value) { $translation = new Translation(); $translation->setPlaceholder($key); $translation->setValue($value); $translation->setComponent($component); $translation->setType($type); $this->translationRepository->add($translation); } $this->persistenceManager->persistAll(); } } Example 2: Custom Export Functionality --------------------------------------- .. code-block:: php use Netresearch\NrTextdb\Domain\Repository\TranslationRepository; class CustomExporter { public function __construct( private readonly TranslationRepository $repository ) {} public function exportToJson(string $component): string { $translations = $this->repository->findByComponent($component); $data = []; foreach ($translations as $translation) { $data[$translation->getPlaceholder()] = $translation->getValue(); } return json_encode($data, JSON_PRETTY_PRINT); } public function exportToCsv(string $component): string { $translations = $this->repository->findByComponent($component); $csv = "Placeholder,Value,Language\n"; foreach ($translations as $translation) { $csv .= sprintf( "%s,%s,%d\n", $translation->getPlaceholder(), $translation->getValue(), $translation->getSysLanguageUid() ); } return $csv; } } Example 3: Frontend Integration -------------------------------- .. code-block:: php use Netresearch\NrTextdb\Service\TranslationService; use TYPO3\CMS\Core\Context\Context; class FrontendTranslations { public function __construct( private readonly TranslationService $translationService, private readonly Context $context ) {} public function getTranslatedMenu(array $menuItems): array { $languageUid = $this->context->getPropertyFromAspect( 'language', 'id' ); foreach ($menuItems as &$item) { $translation = $this->translationService->getTranslation( component: 'menu', type: 'label', placeholder: $item['key'], languageUid: $languageUid ); $item['title'] = $translation?->getValue() ?? $item['title']; } return $menuItems; } } .. _dev-testing: Testing ======= Unit Testing ------------ Example unit test for Translation model: .. code-block:: php namespace Netresearch\NrTextdb\Tests\Unit\Domain\Model; use Netresearch\NrTextdb\Domain\Model\Translation; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; #[CoversClass(Translation::class)] final class TranslationTest extends UnitTestCase { private Translation $subject; protected function setUp(): void { parent::setUp(); $this->subject = new Translation(); } #[Test] public function setValueSetsValue(): void { $value = 'Test translation'; $this->subject->setValue($value); self::assertSame($value, $this->subject->getValue()); } } Functional Testing ------------------ Example functional test for repository: .. code-block:: php namespace Netresearch\NrTextdb\Tests\Functional\Domain\Repository; use Netresearch\NrTextdb\Domain\Repository\TranslationRepository; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; final class TranslationRepositoryTest extends FunctionalTestCase { protected array $testExtensionsToLoad = [ 'typo3conf/ext/nr_textdb', ]; private TranslationRepository $subject; protected function setUp(): void { parent::setUp(); $this->subject = $this->get(TranslationRepository::class); $this->importCSVDataSet(__DIR__ . '/Fixtures/translations.csv'); } public function testFindAllReturnsAllTranslations(): void { $result = $this->subject->findAll(); self::assertCount(10, $result); } } .. _dev-extension-points: Extension Points ================ Extending the TranslationService --------------------------------- .. code-block:: php namespace MyVendor\MyExt\Service; use Netresearch\NrTextdb\Service\TranslationService; class ExtendedTranslationService extends TranslationService { public function getTranslation( string $component, string $type, string $placeholder, int $languageUid = 0 ): ?Translation { // Add custom caching $cacheKey = "{$component}_{$type}_{$placeholder}_{$languageUid}"; if ($cached = $this->cache->get($cacheKey)) { return $cached; } $translation = parent::getTranslation( $component, $type, $placeholder, $languageUid ); $this->cache->set($cacheKey, $translation); return $translation; } } Custom ViewHelper ----------------- .. code-block:: php namespace MyVendor\MyExt\ViewHelpers; use Netresearch\NrTextdb\Service\TranslationService; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; class CustomTranslateViewHelper extends AbstractViewHelper { public function __construct( private readonly TranslationService $translationService ) {} public function initializeArguments(): void { $this->registerArgument('key', 'string', 'Translation key', true); } public function render(): string { $parts = explode('.', $this->arguments['key']); $component = $parts[0] ?? 'default'; $placeholder = $parts[1] ?? $this->arguments['key']; $translation = $this->translationService->getTranslation( component: $component, type: 'label', placeholder: $placeholder ); return $translation?->getValue() ?? $this->arguments['key']; } } .. _dev-best-practices: Best Practices ============== Naming Conventions ------------------ **Components:** * Use lowercase with hyphens: `website`, `shop-cart`, `user-portal` * Be descriptive and consistent **Types:** * Standard types: `label`, `message`, `error`, `notification` * Use singular form: `button` not `buttons` **Placeholders:** * Use dot notation: `page.title`, `button.submit`, `error.validation.email` * Be hierarchical and descriptive Performance ----------- * Cache translation lookups in frontend * Use repository methods instead of manual queries * Batch import/export operations for large datasets * Consider Redis/Memcached for high-traffic sites Code Quality ------------ * Use strict types: `declare(strict_types=1)` * Type-hint all parameters and return values * Write unit tests for business logic * Document public APIs with PHPDoc Security -------- * Escape translation output in templates: `{translation -> f:format.htmlspecialchars()}` * Validate XLIFF files before import * Use prepared statements (Extbase does this automatically) * Restrict file upload permissions .. _dev-troubleshooting: Developer Troubleshooting ========================== ViewHelper Not Working ----------------------- .. code-block:: php // Check namespace registration {namespace textdb=Netresearch\NrTextdb\ViewHelpers} // Verify storage PID configuration $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['nr_textdb']['textDbPid'] Service Injection Failing -------------------------- .. code-block:: yaml # Ensure Services.yaml is configured services: _defaults: autowire: true MyVendor\MyExt\MyClass: public: true # If accessed via GeneralUtility::makeInstance Translation Not Found --------------------- .. code-block:: php // Debug translation lookup $translation = $this->translationService->getTranslation( component: 'website', type: 'label', placeholder: 'test.key', languageUid: 0 ); if (!$translation) { // Check: Component exists? // Check: Type exists? // Check: Record in correct storage folder? // Check: createIfMissing enabled? } .. _dev-resources: Resources ========= * **GitHub Repository:** https://github.com/netresearch/t3x-nr-textdb * **TYPO3 Extension Repository:** https://extensions.typo3.org/extension/nr_textdb * **TYPO3 Core API:** https://docs.typo3.org/m/typo3/reference-coreapi/ * **Extbase Documentation:** https://docs.typo3.org/m/typo3/book-extbasefluid/ * **Fluid ViewHelper Reference:** https://docs.typo3.org/other/typo3/view-helper-reference/