elcheco / translator
Translation system using neon files, supports plurals and string replacements, fallback language. Extension to Nette Framework ^3.0
Installs: 1 614
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
Requires
- php: >=8.3
- ext-intl: *
- ext-tokenizer: *
- dibi/dibi: ^v5.0
- latte/latte: ^3.0
- nette/di: ^3.1
- nette/neon: ^3.0
- nette/safe-stream: ^2.4|^3.0
- nette/utils: ^3.0|^4.0
- psr/log: ^3.0
- symfony/console: ^7.2
Requires (Dev)
- mockery/mockery: ^1.5
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.2
README
A powerful and flexible translation library for PHP applications with support for both NEON files and database storage, featuring full Unicode CLDR plural rules support.
Features
- CLDR Plural Rules Support - Full Unicode CLDR compliance for accurate pluralization
- Legacy Format Compatibility - Seamless support for existing numeric plural formats
- Multiple Storage Backends - NEON files or database storage
- Advanced Pluralization - Support for all CLDR categories (zero, one, two, few, many, other)
- Decimal Number Support - Correct handling of decimal plurals (e.g., Czech uses 'many' for decimals)
- Locale-Specific Number Formatting - Automatic number formatting based on locale
- Translation Usage Tracking - Monitor which translations are actually used
- Console Commands - Import, export, and convert translations
- Migration Tools - Convert legacy formats to CLDR standard
- Fallback Locale Support - Automatic fallback when translations are missing
- Compatible with Nette Framework - Full integration with Nette DI
What's New in Version 2.0
- 🎉 CLDR Plural Rules - Industry-standard pluralization for 100+ languages
- 🔢 Decimal Number Support - Proper handling of fractional numbers in plurals
- 🔄 Automatic Format Detection - Use legacy and CLDR formats in the same project
- 🛠️ Migration Tools - Convert existing translations to CLDR format
- 📊 Enhanced Database Support - Store CLDR patterns and ICU MessageFormat
- ⚡ Zero Breaking Changes - Full backward compatibility maintained
Requirements
- PHP 8.3 or higher
- Nette Framework 3.0 or higher (if using with Nette)
- Dibi database library (if using database storage)
- PHP Intl extension (recommended for CLDR support)
Installation
composer require elcheco/translator
Configuration
Basic Configuration (Nette DI)
extensions: translator: ElCheco\Translator\Extension
NEON File-Based Translations
translator: default: en_US # Default locale fallback: en_US # Fallback locale debugMode: %debugMode% dictionary: factory: ElCheco\Translator\NeonDictionary\NeonDictionaryFactory args: directory: %appDir%/translations # Directory with NEON files cache: %tempDir%/cache/translations # Cache directory autoRefresh: %debugMode% # Auto refresh translations
Database-Based Translations
translator: default: en_US # Default locale fallback: en_US # Fallback locale dictionary: factory: ElCheco\Translator\DbDictionary\DbDictionaryFactory args: - @Dibi\Connection # Database connection - MyModule # Module name - true # Track usage commands: - ElCheco\Translator\Console\ImportNeonTranslationsCommand(@Dibi\Connection) - ElCheco\Translator\Console\ExportNeonTranslationsCommand(@Dibi\Connection) - ElCheco\Translator\Console\ConvertToCldrCommand(@Dibi\Connection) # NEW!
CLDR Plural Rules Support
The translator now supports the Unicode CLDR (Common Locale Data Repository) standard for plural rules, providing linguistically correct pluralization for all languages.
What are CLDR Plural Rules?
CLDR defines standardized categories for plural forms:
- zero: For zero items (some languages have special forms)
- one: For singular (but not always just 1!)
- two: For dual forms (e.g., Slovenian)
- few: For small quantities (e.g., 2-4 in Czech)
- many: For larger quantities or special cases (e.g., decimals in Czech)
- other: The default/general plural form
CLDR vs Legacy Format
The translator supports both formats simultaneously:
Legacy Format (still supported):
messages_count: 0: You have no messages 1: You have %s message 2: You have %s messages # Czech with ranges days_count: 1: %s den "2-4": %s dny 5: %s dní
CLDR Format (recommended):
messages_count: zero: You have no messages one: You have one message other: You have {count} messages # Czech with proper decimal support days_count: one: {count} den few: {count} dny many: {count} dne # for decimals like 1.5, 2.5 other: {count} dní
Special Case: Czech Decimals
Czech (and Slovak) use the many
category specifically for decimal numbers:
# Czech translations distance: one: "{count} kilometr" # 1 kilometr few: "{count} kilometry" # 2, 3, 4 kilometry many: "{count} kilometru" # 1.5 kilometru, 2.5 kilometru other: "{count} kilometrů" # 0, 5, 6... kilometrů
This ensures grammatically correct output:
- ✅ 1,5 kilometru (not "kilometrů" or "kilometr")
- ✅ 2,5 kilometru (not "kilometry")
Usage
In Presenters or Services
use ElCheco\Translator\Translator; class BasePresenter extends \Nette\Application\UI\Presenter { private Translator $translator; public function injectTranslator(Translator $translator) { $this->translator = $translator; } public function beforeRender() { // Set translator for templates $this->template->setTranslator($this->translator); } public function handleSwitchLocale(string $locale) { $this->translator->setLocale($locale); $this->redirect('this'); } }
In Templates (Latte)
{* Simple translation *} <h1>{_'Welcome to our website'}</h1> {* Translation with parameters *} <p>{_'Hello %s', $userName}</p> {* Legacy plural format *} <p>{_'You have %s new messages', $count}</p> {* CLDR format with automatic number formatting *} <p>{_'items_count', $itemCount}</p> {* Decimal numbers are formatted correctly *} <p>{_'distance_km', 1.5}</p> {* Czech: "1,5 kilometru" *}
Direct Usage in PHP
use ElCheco\Translator\Translator; $translator = new Translator($dictionaryFactory); $translator->setLocale('cs_CZ'); // Simple translation echo $translator->translate('Welcome'); // "Vítejte" // Legacy plural format echo $translator->translate('You have %s messages', 5); // "Máte 5 zpráv" // CLDR format echo $translator->translate('days_count', 1); // "1 den" echo $translator->translate('days_count', 2); // "2 dny" echo $translator->translate('days_count', 1.5); // "1,5 dne" (decimal → many) echo $translator->translate('days_count', 5); // "5 dní"
NEON Translation Files
Translation files support both legacy and CLDR formats:
Simple Translations
# en_US.neon Welcome: Welcome to our website "Hello %s": Hello %s
Legacy Plural Format
# cs_CZ.neon "You have %s messages": 0: Nemáte žádné zprávy 1: Máte %s zprávu "2-4": Máte %s zprávy 5: Máte %s zpráv
CLDR Format (Recommended)
# en_US.neon items_count: zero: You have no items one: You have one item other: You have {count} items # cs_CZ.neon items_count: one: Máte {count} položku few: Máte {count} položky many: Máte {count} položky # for decimals other: Máte {count} položek
Command Line Tools
Import Translations from NEON to Database
php bin/console translations:import-neon /path/to/neon/files ModuleName \ --locale=en_US \ --mark-as-translated \ --mark-as-approved \ --overwrite
Export Translations from Database to NEON
php bin/console translations:export-neon ModuleName en_US \ --output-dir=./translations \ --include-keys=key1,key2 \ --include-untranslated
Convert Legacy Translations to CLDR Format (NEW!)
# Convert NEON files php bin/console translations:convert-to-cldr neon /path/to/translations \ --locale=cs_CZ \ --output-dir=./translations/cldr \ --backup # Convert database translations php bin/console translations:convert-to-cldr database ModuleName \ --locale=cs_CZ \ --dry-run
Database Structure
The database schema has been enhanced to support CLDR:
-- New column for format type ALTER TABLE `translation_keys` ADD COLUMN `format_type` ENUM('sprintf', 'icu') DEFAULT 'sprintf'; -- Stores ICU MessageFormat patterns ALTER TABLE `translation_keys` ADD COLUMN `cldr_message_pattern` TEXT NULL;
Number Formatting
The translator automatically formats numbers according to locale:
$translator->setLocale('en_US'); echo $translator->translate('distance_km', 1234.5); // "1,234.5 kilometers" $translator->setLocale('cs_CZ'); echo $translator->translate('distance_km', 1234.5); // "1 234,5 kilometru" $translator->setLocale('de_DE'); echo $translator->translate('distance_km', 1234.5); // "1.234,5 Kilometer"
Migration Guide
Migrating to CLDR Format
- Automatic Detection: The translator automatically detects and handles both formats
- Gradual Migration: You can migrate translations one by one
- Migration Tool: Use the console command to convert existing translations
# Backup and convert
php bin/console translations:convert-to-cldr neon ./translations \
--backup \
--output-dir=./translations
Example Migration
Before (Legacy):
days: 0: žádný den 1: %s den "2-4": %s dny 5: %s dní
After (CLDR):
days: zero: žádný den one: {count} den few: {count} dny many: {count} dne # for decimals other: {count} dní
Advanced Features
Custom Plural Rules
use ElCheco\Translator\Cldr\CldrPluralRules; // Get plural category for a number $category = CldrPluralRules::getPluralCategory('cs_CZ', 1.5); // 'many' // Get available categories for a locale $categories = CldrPluralRules::getAvailableCategories('cs_CZ'); // ['one', 'few', 'many', 'other']
Usage Tracking (Database Only)
// Enable usage tracking $factory = new DbDictionaryFactory($connection, 'Module', true); // Later, save statistics $dictionary->saveUsageStats(); // Query usage data SELECT key, usage_count FROM translation_keys ORDER BY usage_count DESC;
Testing
# Run all tests composer test # Run specific test suites vendor/bin/phpunit --testsuite "CLDR Tests" vendor/bin/phpunit --filter testCzechDecimalSupport
Language Support
The translator includes built-in CLDR plural rules for 100+ languages, including:
- Simple plurals (one/other): English, German, Dutch, Spanish, Italian
- Slavic languages: Czech, Slovak, Polish, Russian, Ukrainian, Croatian
- Complex plurals: Arabic (6 forms), Irish, Maltese, Lithuanian
- No plurals: Japanese, Chinese, Thai, Vietnamese
- Special decimals: Czech/Slovak (many), Lithuanian (many), Romanian (few)
Examples
E-commerce Site
# products.cs_CZ.neon product_count: zero: Žádné produkty one: "{count} produkt" few: "{count} produkty" many: "{count} produktu" # 1,5 produktu other: "{count} produktů" in_stock: zero: Vyprodáno one: "Poslední kus" few: "Posledních {count} kusy" other: "Skladem {count} kusů"
Weather App
# weather.cs_CZ.neon temperature: one: "{count} stupeň" few: "{count} stupně" many: "{count} stupně" # 20,5 stupně other: "{count} stupňů" days_forecast: one: "Předpověď na {count} den" few: "Předpověď na {count} dny" many: "Předpověď na {count} dne" # 1,5 dne other: "Předpověď na {count} dní"
Changelog
Version 2.0.0
- Added full Unicode CLDR plural rules support
- Added decimal number handling for all languages
- Added ICU MessageFormat support
- Added migration tools for legacy → CLDR conversion
- Enhanced database schema for CLDR storage
- Maintained 100% backward compatibility
Version 1.x
- Basic translation support
- Legacy plural forms
- Database and NEON storage
- Import/export commands
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
MIT License
Credits
Inspired by rostenkowski/translate, enhanced with:
- Full CLDR plural rules support
- Decimal number handling
- Modern PHP 8.3+ features
- Enhanced database functionality
- Comprehensive migration tools
Support
For questions and support:
- Create an issue on GitHub
- Check the documentation
- See examples