Translation System
The Fuwafuwa Framework includes a powerful internationalization (i18n) system that makes it easy to reach a global audience. With support for multiple languages, flexible parameter formats, automatic pluralization, and a web-based translation editor, you can create applications that feel native to users anywhere in the world.
Internationalization is more than just translating text—it's about creating an experience that respects cultural and linguistic differences. The translation system handles the technical complexities so you can focus on creating great content. From simple string replacement to complex plural forms with named parameters, the system scales to meet your needs.
One of the key design principles is backward compatibility. Existing applications using the older
{0} parameter format continue to work without any changes, while new applications can
take advantage of the more readable :name format. This gradual migration path means you
can adopt new features at your own pace without breaking existing functionality.
- Backward Compatible - Existing
{0}format continues to work unchanged - Named Parameters - New
:nameformat for better readability and maintainability - Pluralization - Automatic singular/plural form selection based on count
- Fallback Languages - Graceful degradation when translations are missing
- Performance Caching - Automatic file-based caching for fast translation lookups
- Web Translation Editor - Browser-based interface for managing translations
Configuration
The translation system is configured through a simple INI file that controls language settings, caching behavior, and feature flags. This central configuration makes it easy to manage translation settings across your application without touching code.
The lang setting specifies the default language for your application, while
fallback_lang determines which language to use when a translation is missing. Caching
is enabled by default to improve performance, and missing translation logging helps you identify gaps
during development.
Add to your configuration file (e.g., app/configs/translation.ini):
[APP]
lang = en
fallback_lang = en
translation_cache_duration = 3600
log_missing_translations = true
missing_translation_format = {{%s:%s}}
[TRANSLATION]
supported_languages = en,id
enable_pluralization = true
enable_json_files = true
Configuration Options Explained
| Option | Purpose |
|---|---|
lang | Default application language code |
fallback_lang | Language to use when translation is missing |
translation_cache_duration | How long to cache translations (seconds) |
log_missing_translations | Log missing translations for development |
supported_languages | Comma-separated list of available languages |
enable_pluralization | Enable automatic plural form selection |
enable_json_files | Support JSON files alongside PHP arrays |
File Structure
Translation files are organized by language code and functional area, making it easy to manage large translation sets. Each language has its own directory containing PHP files that return translation arrays. This modular organization allows multiple developers or translators to work on different parts of the translation without conflicts.
The directory structure follows a logical pattern: language code first, then functional groupings
like common, auth, register, etc. Nested subdirectories
allow for even finer organization when needed. JSON files can coexist with PHP files, providing
flexibility for different use cases.
app/i18n/trans/
├── en/
│ ├── common.php
│ ├── auth.php
│ ├── register.php
│ ├── content/
│ │ ├── post.php
│ │ └── common.php
│ └── api.json (optional)
└── id/
├── common.php
├── auth.php
├── register.php
└── content/
├── post.php
└── common.php
Basic Usage
Using the translation system in your application is straightforward. The t() function
retrieves translations by their key, which follows a file.key naming convention. This
namespacing keeps translations organized and prevents naming collisions between different parts of
your application.
Translations can be used in both controllers (PHP code) and views (templates). In controllers, call
the t() function directly. In views, use the template syntax to output translated
strings. The system automatically detects the current language and returns the appropriate
translation.
Simple Translation
// In controllers
echo t('common.welcome');
echo t('auth.login_success');
// In views
{{ t('common.save') }}
{{ t('user.profile_updated') }}
Parameter Formats
Many translations need to include dynamic values like usernames, dates, or counts. The translation
system supports two parameter formats: the legacy {0} positional format for backward
compatibility, and the new :name named format for improved readability.
The legacy format works exactly as it always has, requiring no changes to existing code. The new named format makes translations more readable and easier to maintain, especially for complex strings with multiple parameters. Both formats can coexist in the same application, allowing gradual migration.
Legacy Format (Backward Compatible)
// Translation file
'activation_email_sent' => 'Activation email has been sent to {0}',
'user_profile' => 'Profile for {0} ({1})',
// Usage (exactly like before)
echo t('register.activation_email_sent', 'user@example.com');
echo t('example.user_profile', 'john_doe', 'john@example.com');
New Named Format
// Translation file
'hello_user' => 'Hello, :name! Welcome back.',
'user_profile' => 'Profile for :username (:email)',
// Usage with named parameters
echo t('example.hello_user', ['name' => 'John']);
echo t('example.user_profile', [
'username' => 'john_doe',
'email' => 'john@example.com'
]);
Named parameters offer several advantages: they're self-documenting, order-independent (useful for languages with different word orders), and less prone to errors when translations are updated. For new projects or when updating existing translations, consider using the named format.
Pluralization New
Handling singular and plural forms correctly is essential for professional localization. Different
languages have complex pluralization rules, and hardcoding conditionals makes code messy. The
tp() function (translation plural) handles this automatically by selecting the correct
form based on the count.
The system works by looking for two translation keys: the base key for singular form, and the base
key with _plural suffix for the plural form. The count parameter automatically
determines which form to use, and the :count placeholder in the translation is replaced
with the actual number.
Use the tp() function for automatic plural forms:
// Translation file setup
return [
'message' => 'You have :count message',
'message_plural' => 'You have :count messages',
];
// Usage
echo tp('example.message', 1); // "You have 1 message"
echo tp('example.message', 5); // "You have 5 messages"
// With additional parameters
echo tp('example.notification', 3, ['type' => 'email']);
Pluralization isn't just about adding an "s"—many languages have entirely different word forms for different quantities. While the current implementation handles simple singular/plural cases, the foundation is in place for more complex pluralization rules in future versions.
Helper Functions
The translation system provides several helper functions beyond basic t() to handle
common scenarios. These helpers make your code more robust and your user experience more polished,
especially when dealing with optional features or missing translations.
| Function | Description | Example |
|---|---|---|
t($key, ...$args) |
Basic translation | t('common.save') |
tp($key, $count, ...$args) |
Translation with pluralization | tp('items.count', 5) |
t_exists($key, $lang) |
Check if translation exists | t_exists('common.feature') |
t_fallback($key, $fallback, ...$args) |
Translation with custom fallback | t_fallback('key', 'Default') |
t_all($section, $lang) |
Get all translations for a section | t_all('common') |
Checking Translation Existence
When building features that might not have complete translations, it's good practice to check if a
translation exists before using it. The t_exists() function returns true if the
translation key is available in the current or fallback language, allowing you to provide
alternatives or hide untranslated content gracefully.
if (t_exists('common.advanced_feature')) {
echo t('common.advanced_feature');
} else {
echo 'Feature not available';
}
Custom Fallback
Sometimes you need a translation but want to provide a sensible default if it doesn't exist. The
t_fallback() function does exactly this—attempt the translation first, then fall back
to the provided default text. This is particularly useful for optional features or user-generated
content that might not have translations in all languages.
echo t_fallback('optional.feature', 'Default text');
echo t_fallback('user.greeting', 'Hello!', ['name' => 'User']);
JSON File Support
While PHP files are the default format for translations, JSON files provide an alternative that can be more convenient for certain workflows. JSON is particularly useful when translations are managed externally, edited by non-technical translators, or consumed by JavaScript code on the frontend.
JSON files coexist peacefully with PHP translation files, so you can use whichever format makes
sense for each part of your application. The translation system automatically detects and loads
JSON files when enable_json_files is set to true in your configuration.
You can use JSON files alongside PHP arrays:
// app/i18n/trans/en/api.json
{
"success": "Operation successful",
"error": "An error occurred: :message",
"not_found": "Resource not found"
}
Fallback Language Support
In a perfect world, every translation would be available in every supported language. In reality, translations are often incomplete or lag behind new features. The fallback language system ensures your application remains functional even when translations are missing.
When a translation key isn't found in the current language, the system automatically checks the configured fallback language (typically English, the primary development language). This graceful degradation means users always see appropriate content rather than missing translation keys or error messages.
If a translation is not found in the current language, the system automatically falls back to the configured fallback language:
// If 'new_feature.title' doesn't exist in Indonesian,
// it will automatically use the English version
$f3['APP.lang'] = 'id';
echo t('new_feature.title'); // Falls back to English
Web-Based Translation Editor
Managing translations through text files works well for developers, but can be intimidating for translators and content managers. The web-based translation editor provides a user-friendly interface for managing all translations directly in the browser, making it easier for non-technical team members to contribute to localization efforts.
The editor provides a visual overview of all translation keys, with side-by-side views of different languages. Translators can see which translations are missing, search for specific keys, and make changes without touching any code. Changes are exported back to PHP files, keeping the file-based system as the source of truth.
The framework includes a browser-based translation management interface accessible at /admin/i18n-manager.
- Visual editing of all translation files
- Support for nested translation keys
- Add, edit, and delete translations
- Search and filter translations
- Export to PHP files
Caching
Loading translation files on every request would be inefficient, especially for applications with extensive translations. The built-in caching system stores parsed translations in memory, dramatically improving performance while maintaining the simplicity of file-based storage.
The cache automatically invalidates based on the configured duration, or you can clear it manually when translations are updated. The file-based cache means no external services like Redis are required—everything works out of the box with zero configuration.
Translation files are automatically cached to improve performance. Configure cache duration in config.ini:
[APP]
translation_cache_duration = 3600 ; 1 hour in seconds
Missing Translation Logging
During development, it's common to add new features or translation keys before all translations are complete. Missing translation logging helps you identify gaps in your translations by logging every request for a non-existent translation key, along with the requested language.
This feature is invaluable for maintaining translation completeness. By reviewing the logs periodically, you can ensure all user-facing text is properly translated before releases. In production, you may want to disable this logging to reduce noise, or keep it enabled to track missing translations in real-time.
Enable logging to track missing translations during development:
[APP]
log_missing_translations = true
missing_translation_format = {{%s:%s}} ; Format: {{key:lang}}
Missing translations will be logged:
Missing translation: new_feature.title for language: id
Migration from Old System
If you have an existing application using the older translation system, upgrading is painless. The
enhanced system maintains 100% backward compatibility—your existing code continues to work without
any modifications. The {0} positional parameter format, the t() function
signature, and file structure all remain unchanged.
This backward compatibility means you can adopt new features incrementally. Start by enabling the system in configuration, then gradually update to use named parameters and pluralization where they provide the most value. There's no pressure to do a big-bang migration—evolve at your own pace.
The enhanced system is 100% backward compatible. No changes are required to existing code:
// All existing code continues to work unchanged
echo t('common.save');
echo t('common.record_saved', 'User');
echo t('register.activation_email_sent', $email);
// Optionally upgrade to new features when convenient
echo t('common.record_saved', ['type' => 'User']);
echo t('user.welcome', ['name' => $username, 'email' => $email]);
Best Practices
Following these best practices will help you create a maintainable, professional translation system that scales with your application and provides an excellent experience for international users.
Key Recommendations
- 1. Maintain compatibility when possible
- Keep using
{0}format for simple cases where named parameters don't add significant value. This keeps your codebase consistent and easier to understand. - 2. Use named parameters for complex translations
- Switch to
:nameformat when translations have multiple parameters or when parameter order might vary between languages. The readability benefit is substantial. - 3. Use descriptive, hierarchical keys
- Choose keys like
user.profile.update_successinstead of cryptic names likemsg1. Hierarchical keys organize translations logically and prevent naming conflicts. - 4. Group related translations together
- Keep translations for the same feature or module in the same file. This makes it easier for translators to understand context and find related translations.
- 5. Provide fallbacks for optional content
- Use
t_fallback()for features that might not have complete translations, ensuring users see appropriate content rather than missing key errors. - 6. Test missing translations during development
- Enable missing translation logging to identify gaps before releases. Review logs regularly and prioritize translating user-facing content.
- 7. Implement proper pluralization
- Use
tp()for any text that refers to counts. Proper pluralization is a hallmark of professional localization and significantly improves user experience. - 8. Document context for translators
- Add comments in translation files explaining context where needed. What seems obvious to developers may be unclear to translators working with isolated strings.
Consider establishing a translation workflow where developers add new keys with English text as the default, then non-technical translators use the web editor to provide translations for other languages. This separation of concerns allows each role to focus on their strengths while maintaining translation quality.
:name format for complex translationsuser.profile.update_success instead of msg1t_fallback() for optional features