historyheraldry / ci4-database-translations
Database-backed translations and deployable translation seed generation for CodeIgniter 4.
Package info
github.com/HistoryandHeraldry/ci4-database-translations
pkg:composer/historyheraldry/ci4-database-translations
Requires
- php: ^8.2
- codeigniter4/framework: ^4.7
- symfony/uid: ^7.2
Requires (Dev)
- michalsn/codeigniter4-uuid: ^2.0
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.5 || ^12.0 || ^13.0
Suggests
- historyheraldry/ci4-seed-tracker: Runs generated translation seeds once and records their checksums.
- michalsn/codeigniter4-uuid: Provides UUID primary keys through the included optional adapter.
Provides
None
Conflicts
None
Replaces
None
README
Database-backed translations for CodeIgniter 4, with native language-file fallback and a deployable NDJSON seed generator.
Requirements
- PHP 8.2 or later
- CodeIgniter 4.7 or later
Installation
composer require historyheraldry/ci4-database-translations
Choose the primary-key mode before running the migration, then create the table:
php spark migrate -n 'HistoryHeraldry\DatabaseTranslations'
The migration creates a prefixed translations table with a unique key over locale, namespace, and key.
Register the language service
Override the language service in app/Config/Services.php:
use CodeIgniter\Language\Language; use HistoryHeraldry\DatabaseTranslations\Config\DatabaseTranslations; use HistoryHeraldry\DatabaseTranslations\Language\DatabaseLanguage; use HistoryHeraldry\DatabaseTranslations\Repositories\TranslationRepository; public static function language(?string $locale = null, bool $getShared = true) { $locale ??= config('App')->defaultLocale; if ($getShared) { return static::getSharedInstance('language', $locale); } try { $config = config('DatabaseTranslations'); $repository = new TranslationRepository(config: $config); if ($repository->tableExists()) { return new DatabaseLanguage($locale, $repository, $config); } } catch (\Throwable $exception) { log_message('warning', 'Database translations unavailable: {message}', ['message' => $exception->getMessage()]); } return new Language($locale); }
Existing calls such as lang('Common.actions.button.save') continue to use CI4's native language API. Database rows use separate namespace and key values:
locale: en
namespace: Common
key: actions.button.save
value: Save
By default, language files override matching database values. This keeps files useful as the development source while the database supplies missing runtime values.
Generate translation seeds
Place translations in standard CI4 files such as app/Language/en/Common.php, then run:
php spark translations:generate-seeder
Pass a seeder class name when useful:
php spark translations:generate-seeder InitialCatalogueSeeder php spark translations:generate-seeder --dry-run php spark translations:generate-seeder InitialCatalogueSeeder --keep-files
The command recursively flattens nested translation arrays and creates a seeder under app/Database/Seeds plus a matching NDJSON file under app/Database/Seeds/Data/Translations.
When historyheraldry/ci4-seed-tracker is installed, the seeder filename automatically receives its required timestamp prefix. Apply it once with:
php spark seed
Without the seed tracker, the command creates a standard CI4 seeder without a timestamp prefix. Run it with the class name shown by the command:
php spark db:seed TranslationsSeeder
Commit both generated files.
After successfully creating both artifacts, the command replaces the returned translation array in each contributing language file with an empty array. Content outside that returned array, including file headers and comments, remains intact.
Both options are presence-only switches; they do not take values:
--dry-runpreviews the paths and row count without writing artifacts or changing language files.--keep-filescreates the seed and NDJSON artifacts while leaving language files unchanged.
Configuration
Create app/Config/DatabaseTranslations.php when defaults need changing:
<?php namespace Config; use HistoryHeraldry\DatabaseTranslations\Config\DatabaseTranslations as BaseConfig; final class DatabaseTranslations extends BaseConfig { public string $databaseGroup = 'default'; public string $table = 'translations'; public bool $filesOverrideDatabase = true; }
The configured database group's table prefix is applied automatically.
Primary-key modes
The default mode is dependency-free auto-incrementing integer IDs:
public string $primaryKeyMode = self::PRIMARY_KEY_AUTO_INCREMENT;
To use UUIDs, configure UUID mode, a supported version, and an identifier generator before running the migration. The package supports the uniqueness-safe UUID versions v1, v4, v6, and v7; UUIDv4 is the default.
use HistoryHeraldry\DatabaseTranslations\Identifiers\SymfonyUuidGenerator; public string $primaryKeyMode = self::PRIMARY_KEY_UUID; public string $uuidVersion = self::UUID_VERSION_7; public ?string $identifierGenerator = SymfonyUuidGenerator::class;
SymfonyUuidGenerator is included with this package, so consumers do not need to install another UUID integration. The existing RandomUuidGenerator remains available for applications that specifically require its dependency-free UUIDv4 implementation.
Applications using michalsn/codeigniter4-uuid may instead install and configure that package, then select its adapter. The adapter uses the same uuidVersion setting:
composer require michalsn/codeigniter4-uuid
use HistoryHeraldry\DatabaseTranslations\Identifiers\MichalsnUuidGenerator; public string $primaryKeyMode = self::PRIMARY_KEY_UUID; public string $uuidVersion = self::UUID_VERSION_4; public ?string $identifierGenerator = MichalsnUuidGenerator::class;
UUIDv3 and UUIDv5 are deliberately not offered for automatic primary keys. They are name-based and deterministic, so repeatedly generating them from a fixed namespace and name can produce duplicate identifiers. Consumers needing a specialised scheme may implement HistoryHeraldry\DatabaseTranslations\Contracts\IdentifierGenerator.
Custom generators may be used by implementing HistoryHeraldry\DatabaseTranslations\Contracts\IdentifierGenerator and configuring the implementation's class name.
The selected primary-key mode is part of the database schema. Do not change it after running the migration without writing a dedicated schema and data-conversion migration.
Reading translations directly
$repository = new TranslationRepository(); $exact = $repository->getForLocaleAndNamespaces('en-GB', ['Common', 'Checkout']); $withFallback = $repository->getForLocaleAndNamespacesWithFallback('en-GB', ['Common']);
Fallback order is the application's default locale, base locale, then exact locale; later values override earlier ones.
Optional model
Applications that want conventional CI4 CRUD and query-builder access may use the included model:
use HistoryHeraldry\DatabaseTranslations\Models\TranslationModel; $translations = new TranslationModel(); $id = $translations->insert([ 'locale' => 'en', 'namespace' => 'Common', 'key' => 'actions.button.save', 'value' => 'Save', ]); $english = $translations->where('locale', 'en')->findAll();
The model reads the configured database group, table, and primary-key mode. It provides validation, timestamps, soft deletes, and configured UUID generation. It is optional: runtime language loading and generated seeders continue to use TranslationRepository, and applications may keep their own model layer.
Licence
Released under the MIT License.