Developer Manual 

Overview 

This section covers integration of the TextDB extension into your TYPO3 project, including ViewHelpers, APIs, services, and extension points.

Architecture 

Domain Model 

The extension uses Extbase domain-driven design:

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
Copied!

Dependency Injection 

All services use constructor injection via Configuration/Services.yaml:

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    Netresearch\NrTextdb\:
        resource: '../Classes/*'
        exclude: '../Classes/Domain/Model/*'
Copied!

ViewHelpers 

TextDB ViewHelper 

Main ViewHelper for displaying translations in Fluid templates.

Namespace:

{namespace textdb=Netresearch\NrTextdb\ViewHelpers}
Copied!

Usage:

<textdb:textdb
    component="website"
    type="label"
    placeholder="welcome.message"
    default="Welcome!"
/>
Copied!

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:

<!-- Simple usage -->
<h1><textdb:textdb component="website" type="label" placeholder="page.title" /></h1>

<!-- With default value -->
<p>
    <textdb:textdb
        component="website"
        type="message"
        placeholder="welcome.text"
        default="Welcome to our website!"
    />
</p>

<!-- Inline syntax -->
{textdb:textdb(component: 'website', type: 'label', placeholder: 'button.submit')}
Copied!

Translate ViewHelper 

Alternative ViewHelper compatible with f:translate interface.

Usage:

{namespace textdb=Netresearch\NrTextdb\ViewHelpers}

<textdb:translate key="LLL:EXT:my_ext:path.to.key" />
Copied!

Component Configuration:

Set component in controller:

use Netresearch\NrTextdb\ViewHelpers\TranslateViewHelper;

class MyController extends ActionController
{
    public function initializeAction(): void
    {
        TranslateViewHelper::$component = 'my-component';
    }
}
Copied!

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

Services API 

TranslationService 

Core service for translation management.

Injection:

use Netresearch\NrTextdb\Service\TranslationService;

class MyClass
{
    public function __construct(
        private readonly TranslationService $translationService
    ) {}
}
Copied!

Methods:

getTranslation() 

public function getTranslation(
    string $component,
    string $type,
    string $placeholder,
    int $languageUid = 0
): ?Translation
Copied!

Get translation record.

Example:

$translation = $this->translationService->getTranslation(
    component: 'website',
    type: 'label',
    placeholder: 'welcome.message',
    languageUid: 1
);

if ($translation) {
    echo $translation->getValue();
}
Copied!

createTranslation() 

public function createTranslation(
    string $component,
    string $type,
    string $placeholder,
    string $value,
    int $languageUid = 0
): Translation
Copied!

Create new translation record.

Example:

$translation = $this->translationService->createTranslation(
    component: 'shop',
    type: 'label',
    placeholder: 'cart.add',
    value: 'Add to cart',
    languageUid: 0
);
Copied!

ImportService 

Service for importing XLIFF files.

Injection:

use Netresearch\NrTextdb\Service\ImportService;

public function __construct(
    private readonly ImportService $importService
) {}
Copied!

Methods:

importXliffFile() 

public function importXliffFile(
    string $filePath,
    bool $overwriteExisting = false
): array
Copied!

Import translations from XLIFF file.

Returns: Array with import statistics

[
    'imported' => 150,
    'updated' => 25,
    'skipped' => 10,
    'errors' => []
]
Copied!

Example:

$result = $this->importService->importXliffFile(
    filePath: '/path/to/translations.xlf',
    overwriteExisting: true
);

echo "Imported: {$result['imported']} translations";
Copied!

Repositories 

TranslationRepository 

Repository for translation records.

Custom Methods:

findByComponentAndType() 

public function findByComponentAndType(
    Component $component,
    Type $type
): QueryResultInterface
Copied!

Find all translations for component and type.

findByPlaceholder() 

public function findByPlaceholder(
    string $placeholder,
    int $languageUid = 0
): ?Translation
Copied!

Find translation by placeholder key.

Example:

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
    }
}
Copied!

Domain Models 

Translation Model 

Main translation entity.

Properties:

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;
}
Copied!

Getters/Setters:

$translation = new Translation();
$translation->setPlaceholder('welcome.message');
$translation->setValue('Welcome!');
$translation->setComponent($component);
$translation->setType($type);

echo $translation->getValue(); // "Welcome!"
Copied!

Component Model 

class Component extends AbstractEntity
{
    protected string $name = '';
    protected string $identifier = '';
}
Copied!

Type Model 

class Type extends AbstractEntity
{
    protected string $name = '';
    protected string $identifier = '';
}
Copied!

Environment Model 

class Environment extends AbstractEntity
{
    protected string $name = '';
    protected string $identifier = '';
}
Copied!

Console Commands 

ImportCommand 

CLI command for importing translations.

Location: Classes/Command/ImportCommand.php

Usage:

vendor/bin/typo3 nr_textdb:import [file-path]
Copied!

Configuration:

# Configuration/Services.yaml
Netresearch\NrTextdb\Command\ImportCommand:
    tags:
        - name: 'console.command'
          command: 'nr_textdb:import'
          description: 'Imports textdb records from language files'
          schedulable: false
Copied!

Creating Custom Commands:

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;
    }
}
Copied!

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

API Examples 

Example 1: Programmatic Translation Management 

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();
    }
}
Copied!

Example 2: Custom Export Functionality 

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;
    }
}
Copied!

Example 3: Frontend Integration 

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;
    }
}
Copied!

Testing 

Unit Testing 

Example unit test for Translation model:

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());
    }
}
Copied!

Functional Testing 

Example functional test for repository:

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);
    }
}
Copied!

Extension Points 

Extending the TranslationService 

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;
    }
}
Copied!

Custom ViewHelper 

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'];
    }
}
Copied!

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

Developer Troubleshooting 

ViewHelper Not Working 

// Check namespace registration
{namespace textdb=Netresearch\NrTextdb\ViewHelpers}

// Verify storage PID configuration
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['nr_textdb']['textDbPid']
Copied!

Service Injection Failing 

# Ensure Services.yaml is configured
services:
    _defaults:
        autowire: true

    MyVendor\MyExt\MyClass:
        public: true  # If accessed via GeneralUtility::makeInstance
Copied!

Translation Not Found 

// 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?
}
Copied!

Resources