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
Dependency Injection
All services use constructor injection via Configuration/:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Netresearch\NrTextdb\:
resource: '../Classes/*'
exclude: '../Classes/Domain/Model/*'
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>
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>
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
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
Translation Process:
- Translators contribute via https://crowdin.com/project/typo3-cms
- TYPO3 translation coordinators review submissions
- Approved translations automatically sync to repository
- Changes included in next extension release
Adding New Languages:
To add a new language:
- Create language files following naming convention:
{lang}.locallang*.xlf - Copy structure from English source files
- Update
crowdin.ymlwith new language patterns - 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
-
- Type
- string
- Required
- true
Component identifier for organizing translations (e.g., "website", "shop", "checkout").
type
-
- Type
- string
- Required
- true
Type identifier categorizing the translation (e.g., "label", "message", "error", "button").
placeholder
-
- Type
- string
- Required
- true
Unique translation key within the component and type context.
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')}
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" />
Component Configuration:
Set component in controller:
use Netresearch\NrTextdb\ViewHelpers\TranslateViewHelper;
class MyController extends ActionController
{
public function initializeAction(): void
{
TranslateViewHelper::$component = 'my-component';
}
}
Auto-Import Feature:
When enabled (create), this ViewHelper will:
- Load translation from XLIFF file on first request
- Create TextDB record automatically
- 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();
}
createTranslation()
public function createTranslation(
string $component,
string $type,
string $placeholder,
string $value,
int $languageUid = 0
): Translation
Create new translation record.
Example:
$translation = $this->translationService->createTranslation(
component: 'shop',
type: 'label',
placeholder: 'cart.add',
value: 'Add to cart',
languageUid: 0
);
ImportService
Service for importing XLIFF files.
Injection:
use Netresearch\NrTextdb\Service\ImportService;
public function __construct(
private readonly ImportService $importService
) {}
Methods:
importXliffFile()
public function importXliffFile(
string $filePath,
bool $overwriteExisting = false
): array
Import translations from XLIFF file.
Returns: Array with import statistics
[
'imported' => 150,
'updated' => 25,
'skipped' => 10,
'errors' => []
]
Example:
$result = $this->importService->importXliffFile(
filePath: '/path/to/translations.xlf',
overwriteExisting: true
);
echo "Imported: {$result['imported']} translations";
Repositories
TranslationRepository
Repository for translation records.
Custom Methods:
findByComponentAndType()
public function findByComponentAndType(
Component $component,
Type $type
): QueryResultInterface
Find all translations for component and type.
findByPlaceholder()
public function findByPlaceholder(
string $placeholder,
int $languageUid = 0
): ?Translation
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
}
}
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;
}
Getters/Setters:
$translation = new Translation();
$translation->setPlaceholder('welcome.message');
$translation->setValue('Welcome!');
$translation->setComponent($component);
$translation->setType($type);
echo $translation->getValue(); // "Welcome!"
Component Model
class Component extends AbstractEntity
{
protected string $name = '';
protected string $identifier = '';
}
Type Model
class Type extends AbstractEntity
{
protected string $name = '';
protected string $identifier = '';
}
Environment Model
class Environment extends AbstractEntity
{
protected string $name = '';
protected string $identifier = '';
}
Console Commands
ImportCommand
CLI command for importing translations.
Location: Classes/
Usage:
vendor/bin/typo3 nr_textdb:import [file-path]
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
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;
}
}
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();
}
}
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;
}
}
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;
}
}
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());
}
}
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);
}
}
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;
}
}
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'];
}
}
Best Practices
Naming Conventions
- Components:
-
- Use lowercase with hyphens:
website,shop-,cart user-portal - Be descriptive and consistent
- Use lowercase with hyphens:
- Types:
-
- Standard types:
label,message,error,notification - Use singular form:
buttonnotbuttons
- Standard types:
- Placeholders:
-
- Use dot notation:
page.,title button.,submit error.validation. email - Be hierarchical and descriptive
- Use dot notation:
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']
Service Injection Failing
# Ensure Services.yaml is configured
services:
_defaults:
autowire: true
MyVendor\MyExt\MyClass:
public: true # If accessed via GeneralUtility::makeInstance
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?
}
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/