-
Notifications
You must be signed in to change notification settings - Fork 498
Localization Management
This comprehensive guide covers the complete workflow for managing translations in ChurchCRM, including POEditor integration, term extraction, and the process of converting hardcoded strings into translatable terms.
- Overview
- POEditor Integration
- Term Extraction Process
- Converting Hardcoded Strings to Terms
- Generating and Uploading messages.po
- Downloading and Deploying Translations
- Troubleshooting
ChurchCRM uses a comprehensive localization system that combines:
- GNU Gettext for PHP internationalization
- POEditor as the primary translation management platform
- i18next for JavaScript/React translations
- Automated extraction tools for comprehensive term coverage
gettext('Text to translate')
_('Text to translate') // Shorthand alias
ngettext('singular', 'plural', $count) // Plural formsi18next.t('Text to translate')
t('Text to translate') // In React components{{ gettext('Text to translate') }}- POEditor Account: Create or access your POEditor project
- API Credentials: Obtain project ID and API token
-
Configuration: Set up
BuildConfig.jsonwith credentials
Create or update BuildConfig.json in the project root:
{
"POEditor": {
"id": "YOUR_PROJECT_ID",
"token": "YOUR_API_TOKEN"
}
}-
Project Settings:
- Set base language to English (en)
- Enable automatic translation suggestions
- Configure contributor permissions
-
Language Setup:
- Add target languages matching
src/locale/locales.json - Set proper locale codes (e.g.,
pt-br,es-es,de-de)
- Add target languages matching
ChurchCRM uses a comprehensive extraction system that captures terms from multiple sources:
Run the complete extraction process:
npm run locale:term-extractThis script performs the following steps:
Extracts translatable terms from database tables including:
- User configuration tooltips (
userconfig_ucfg) - Query names and descriptions (
query_qry) - Query parameter options (
queryparameteroptions_qpo) - Query parameters (
queryparameters_qrp)
Generated: ~112 unique database terms with proper context
Extracts country names and locale identifiers:
- Authoritative country data from PHP Countries class
- Multilingual country names (e.g., "China (中国)")
- Locale names from
locales.json
Generated: ~297 static data terms
Uses GNU xgettext to scan PHP files:
- Scans all PHP files in
src/directory - Excludes vendor directories automatically
- Captures
gettext(),_(), andngettext()calls
Generated: ~1,800+ terms from application logic
Uses i18next-parser for modern JavaScript:
- Scans React components (.tsx files)
- Extracts
t()andi18next.t()calls - Generates JSON format, converts to PO
Generated: ~97 JavaScript terms
Uses msgcat to combine all sources:
- Merges with
--use-firstpreference - Handles duplicate terms properly
- Produces final
locale/messages.po
Final Result: 2,292+ total translatable terms
For specific term types, run individual extractors:
# Database terms only
node locale/scripts/locale-extract-db.js
# Static data only
node locale/scripts/locale-extract-static.jsWhen you find hardcoded strings in the code that should be translatable, follow this process:
Look for user-facing text that isn't wrapped in translation functions:
// ❌ Hardcoded string
echo "User not found";
// ❌ Hardcoded in arrays
$messages = [
'error' => 'An error occurred',
'success' => 'Operation completed'
];
// ❌ Hardcoded in HTML
<h1>Church Directory</h1>Convert hardcoded strings to use appropriate translation functions:
// ✅ Basic translation
echo gettext("User not found");
// or
echo _("User not found");
// ✅ In arrays
$messages = [
'error' => gettext('An error occurred'),
'success' => gettext('Operation completed')
];
// ✅ Variables
$title = gettext('Church Directory');
echo "<h1>$title</h1>";
// ✅ Plural forms
$count = 5;
echo ngettext('1 member', '%d members', $count);// ✅ In React components
function UserList() {
return (
<div>
<h1>{t('User Directory')}</h1>
<button>{t('Add New User')}</button>
</div>
);
}
// ✅ In vanilla JavaScript
const message = i18next.t('Processing your request...');{# ✅ In templates #}
<h1>{{ gettext('Welcome to ChurchCRM') }}</h1>
<p>{{ gettext('Manage your congregation effectively') }}</p>For strings that might have different meanings in different contexts:
// ✅ With context (PHP doesn't support this directly, but document it)
gettext('State'); // Could mean US state or condition
// Use descriptive strings instead
gettext('US State');
gettext('Family Status');For strings with variable content:
// ✅ Use sprintf for variables
$message = sprintf(
gettext('Welcome back, %s!'),
$userName
);
// ✅ For complex plurals
$message = sprintf(
ngettext(
'You have %d new message',
'You have %d new messages',
$count
),
$count
);-
Keep strings complete: Don't break sentences into parts
// ❌ Don't do this gettext('Welcome') . ' ' . $name . ' ' . gettext('to ChurchCRM'); // ✅ Do this sprintf(gettext('Welcome %s to ChurchCRM'), $name);
-
Use meaningful strings: Avoid technical abbreviations
// ❌ Technical gettext('Usr mgmt'); // ✅ Descriptive gettext('User Management');
-
Consider screen space: Keep translations concise
// ✅ Concise but clear gettext('Save Changes'); gettext('Delete User');
After converting hardcoded strings to translation functions:
# Extract all terms
npm run locale:term-extractThis creates locale/messages.po with all translatable terms.
Check the generated file contains your new terms:
# Search for your new terms
grep -n "Your new term" locale/messages.po
# Check total term count
grep -c "^msgid " locale/messages.po-
Log into POEditor:
- Go to your ChurchCRM project
- Navigate to "Import" section
-
Upload Process:
- Choose "Import from file"
- Select
locale/messages.po - Choose import options:
- ✅ "Also import translations" (if any exist)
- ✅ "Sync terms (remove terms that are not found in the file)"
- ✅ "Fuzzy matching"
-
Verify Upload:
- Check term count matches your local file
- Verify new terms appear in the interface
- Confirm existing translations are preserved
If you have API access configured, you can automate the upload:
# Using Grunt (if configured)
grunt poeditor:uploadTerms
# Or using POEditor API directly
curl -X POST https://api.poeditor.com/v2/projects/upload \
-F api_token="YOUR_API_TOKEN" \
-F id="YOUR_PROJECT_ID" \
-F updating="terms_translations" \
-F file=@"locale/messages.po"Download completed translations:
npm run locale:downloadThis downloads translated .po files to src/locale/textdomain/*/LC_MESSAGES/messages.po.
Check that translations were downloaded properly:
# List downloaded languages
ls -la src/locale/textdomain/
# Check specific language file
head -20 src/locale/textdomain/es_ES/LC_MESSAGES/messages.poThe download process automatically generates:
-
Compiled Gettext files (
.mo): For PHP runtime - JavaScript locale files: For browser internationalization
- JSON mappings: For key-value lookups
-
Change system locale:
- Go to Settings → Localization
- Select a translated language
- Verify UI displays in chosen language
-
Check specific terms:
- Navigate to areas where you added new terms
- Confirm translations appear correctly
- Test plural forms if applicable
Monitor translation completeness:
# Generate audit report
npm run locale:audit
# View completion status
cat locale/poeditor-audit.mdProblem: New translation calls not appearing in messages.po
Solutions:
- Ensure you're using correct function names (
gettext,_,t) - Check file is in scanned directories (
src/for PHP) - Verify syntax is correct (matching quotes, proper parameters)
- Re-run extraction:
npm run locale:term-extract
Problem: Cannot upload messages.po to POEditor
Solutions:
- Check POEditor API credentials in
BuildConfig.json - Verify project ID and token are correct
- Ensure file format is valid UTF-8
- Try uploading manually through POEditor interface
Problem: UI still shows English after downloading translations
Solutions:
- Verify
.mofiles are generated:find src/locale -name "*.mo" - Check Apache/web server has permission to read files
- Clear browser cache and cookies
- Verify system locale is set correctly in Settings
Problem: Same term appears multiple times in different contexts
Solutions:
- Use more specific strings instead of generic ones
- Add comments to clarify context:
// Context: Navigation menu gettext('Home'); // Context: Address field gettext('Home Address');
Problem: Extraction scripts fail or miss terms
Solutions:
- Install required dependencies:
npm install - Ensure
xgettextis installed:which xgettext - Check file permissions in
locale/directory - Run individual extractors to isolate issues
# Check locale configuration
cat src/locale/locales.json
# Verify extracted terms
head -20 locale/messages.po
# Check generated JavaScript files
ls -la src/locale/js/
# Validate Gettext compilation
find src/locale/textdomain -name "*.mo"
# Test specific language
grep -A5 -B5 "your term" src/locale/textdomain/es_ES/LC_MESSAGES/messages.po-
Documentation: Review
locale/README.mdfor technical details -
Translation Status: Run
npm run locale:auditfor completeness reports - Community: Check ChurchCRM forums and GitHub issues
- POEditor Support: Use POEditor documentation for platform-specific issues
-
Always extract before uploading: Run
npm run locale:term-extract - Use descriptive strings: Avoid abbreviations and technical jargon
- Test thoroughly: Verify translations in actual UI contexts
- Keep terms complete: Don't fragment sentences
- Monitor regularly: Check translation completeness with audit reports
- Document context: Add comments for ambiguous terms
- Version control: Commit translation files to track changes
This workflow ensures comprehensive localization coverage and maintains high-quality translations across all supported languages in ChurchCRM.
- Installation Guide ← Start here!
- First Run Setup
- Features Overview
- Upgrade Guide
- Backup & Restore
- Rollback Procedures
- File Permissions
- Troubleshooting
- Logging & Diagnostics
- SSL/HTTPS Security
- Localization