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!

Localization Infrastructure 

New in version 3.1.0

Comprehensive localization infrastructure with 23 languages and Crowdin integration.

The extension includes a robust localization infrastructure supporting 23 languages for the backend interface.

Supported Languages 

European (13): Afrikaans (af), Czech (cs), Danish (da), German (de), Spanish (es), Finnish (fi), French (fr), Italian (it), Dutch (nl), Norwegian (no), Polish (pl), Portuguese (pt), Swedish (sv)

Asian & African (10): Arabic (ar), Hindi (hi), Indonesian (id), Japanese (ja), Korean (ko), Russian (ru), Swahili (sw), Thai (th), Vietnamese (vi), Chinese (zh)

Technical Implementation 

XLIFF 1.2 Standard

All translation files follow XLIFF 1.2 specification:

<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="en" datatype="plaintext" original="EXT:nr_textdb/Resources/Private/Language/locallang.xlf" date="..." product-name="nr_textdb">
        <header/>
        <body>
            <trans-unit id="module.title" resname="module.title" translate="no">
                <source>Netresearch</source>
            </trans-unit>
        </body>
    </file>
</xliff>
Copied!

Proper Names Protection

Brand names are marked as untranslatable using translate="no" attribute:

<trans-unit id="module.title" resname="module.title" translate="no">
    <source>Netresearch</source>
</trans-unit>
Copied!

This ensures "Netresearch" and "TextDb" remain unchanged across all translations.

UTF-8 Encoding

All language files use UTF-8 encoding to support non-Latin scripts:

  • Right-to-left scripts: Arabic (العربية)
  • Asian ideographs: Chinese (中文), Japanese (日本語), Korean (한국어)
  • Indic scripts: Hindi (हिन्दी), Thai (ไทย)

File Structure 

Each language has 5 translation files:

Resources/Private/Language/
├── {lang}.locallang.xlf           # General interface labels
├── {lang}.locallang_db.xlf        # Database field labels
├── {lang}.locallang_mod.xlf       # Backend module labels
├── {lang}.locallang_mod_sync.xlf  # Sync module labels
└── {lang}.locallang_mod_textdb.xlf # TextDB module labels
Copied!

Total: 116 XLIFF files (23 languages × 5 files + 1 source file per type)

Community Translation Workflow 

The extension integrates with TYPO3's centralized Crowdin translation system:

Configuration (crowdin.yml):

files:
  - source: Resources/Private/Language/locallang.xlf
    translation: Resources/Private/Language/%two_letters_code%.locallang.xlf
  - source: Resources/Private/Language/locallang_db.xlf
    translation: Resources/Private/Language/%two_letters_code%.locallang_db.xlf
  # ... additional file types
Copied!

Translation Process:

  1. Translators contribute via https://crowdin.com/project/typo3-cms
  2. TYPO3 translation coordinators review submissions
  3. Approved translations automatically sync to repository
  4. Changes included in next extension release

Adding New Languages:

To add a new language:

  1. Create language files following naming convention: {lang}.locallang*.xlf
  2. Copy structure from English source files
  3. Update crowdin.yml with new language patterns
  4. Submit to Crowdin for community translation

See CONTRIBUTING.md for detailed translation contribution guidelines.

ViewHelpers 

TextDB ViewHelper 

class TextdbViewHelper

Main ViewHelper for displaying translations in Fluid templates.

Namespace

Netresearch\NrTextdb\ViewHelpers

Extends

TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper

Usage:

{namespace textdb=Netresearch\NrTextdb\ViewHelpers}

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

Parameters:

component

component
Type
string
Required
true

Component identifier for organizing translations (e.g., "website", "shop", "checkout").

type

type
Type
string
Required
true

Type identifier categorizing the translation (e.g., "label", "message", "error", "button").

placeholder

placeholder
Type
string
Required
true

Unique translation key within the component and type context.

default

default
Type
string
Required
false
Default
null

Fallback text displayed if the translation is not found in the database.

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 

class TranslationService

Core service for translation management and retrieval.

Namespace

Netresearch\NrTextdb\Service

Dependency Injection:

use Netresearch\NrTextdb\Service\TranslationService;

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

Methods:

getTranslation ( string $component, string $type, string $placeholder, int $languageUid = 0)

Retrieves a translation record from the database.

param string $component

Component identifier

param string $type

Type identifier

param string $placeholder

Translation key

param int $languageUid

Language UID (0 for default language)

returntype

\\Netresearch\\NrTextdb\\Domain\\Model\\Translation|null

Example:

Returns

Translation domain model or null if not found

$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