Skip to content

Localization Management

George Dawoud edited this page Oct 12, 2025 · 1 revision

ChurchCRM Localization Management Guide

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.

Table of Contents

  1. Overview
  2. POEditor Integration
  3. Term Extraction Process
  4. Converting Hardcoded Strings to Terms
  5. Generating and Uploading messages.po
  6. Downloading and Deploying Translations
  7. Troubleshooting

Overview

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

Supported Translation Functions

PHP

gettext('Text to translate')
_('Text to translate')                    // Shorthand alias
ngettext('singular', 'plural', $count)    // Plural forms

JavaScript/React

i18next.t('Text to translate')
t('Text to translate')                    // In React components

Twig Templates

{{ gettext('Text to translate') }}

POEditor Integration

Setup Requirements

  1. POEditor Account: Create or access your POEditor project
  2. API Credentials: Obtain project ID and API token
  3. Configuration: Set up BuildConfig.json with credentials

BuildConfig.json Setup

Create or update BuildConfig.json in the project root:

{
  "POEditor": {
    "id": "YOUR_PROJECT_ID",
    "token": "YOUR_API_TOKEN"
  }
}

POEditor Project Configuration

  1. Project Settings:

    • Set base language to English (en)
    • Enable automatic translation suggestions
    • Configure contributor permissions
  2. Language Setup:

    • Add target languages matching src/locale/locales.json
    • Set proper locale codes (e.g., pt-br, es-es, de-de)

Term Extraction Process

ChurchCRM uses a comprehensive extraction system that captures terms from multiple sources:

Automatic Term Extraction

Run the complete extraction process:

npm run locale:term-extract

This script performs the following steps:

1. Database Terms Extraction

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

2. Static Data Extraction

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

3. PHP Source Code Extraction

Uses GNU xgettext to scan PHP files:

  • Scans all PHP files in src/ directory
  • Excludes vendor directories automatically
  • Captures gettext(), _(), and ngettext() calls

Generated: ~1,800+ terms from application logic

4. JavaScript/React Extraction

Uses i18next-parser for modern JavaScript:

  • Scans React components (.tsx files)
  • Extracts t() and i18next.t() calls
  • Generates JSON format, converts to PO

Generated: ~97 JavaScript terms

5. File Merging

Uses msgcat to combine all sources:

  • Merges with --use-first preference
  • Handles duplicate terms properly
  • Produces final locale/messages.po

Final Result: 2,292+ total translatable terms

Manual Term Extraction

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.js

Converting Hardcoded Strings to Terms

When you find hardcoded strings in the code that should be translatable, follow this process:

Step 1: Identify Hardcoded Strings

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>

Step 2: Wrap Strings in Translation Functions

Convert hardcoded strings to use appropriate translation functions:

PHP Examples

// ✅ 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);

JavaScript Examples

// ✅ 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...');

Twig Examples

{# ✅ In templates #}
<h1>{{ gettext('Welcome to ChurchCRM') }}</h1>
<p>{{ gettext('Manage your congregation effectively') }}</p>

Step 3: Use Context for Disambiguation

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');

Step 4: Handle Dynamic Content

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
);

Best Practices for String Conversion

  1. 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);
  2. Use meaningful strings: Avoid technical abbreviations

    // ❌ Technical
    gettext('Usr mgmt');
    
    // ✅ Descriptive
    gettext('User Management');
  3. Consider screen space: Keep translations concise

    // ✅ Concise but clear
    gettext('Save Changes');
    gettext('Delete User');

Generating and Uploading messages.po

Step 1: Generate Complete Term File

After converting hardcoded strings to translation functions:

# Extract all terms
npm run locale:term-extract

This creates locale/messages.po with all translatable terms.

Step 2: Verify Generated File

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

Step 3: Upload to POEditor

  1. Log into POEditor:

    • Go to your ChurchCRM project
    • Navigate to "Import" section
  2. 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"
  3. Verify Upload:

    • Check term count matches your local file
    • Verify new terms appear in the interface
    • Confirm existing translations are preserved

Alternative: Automated Upload

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"

Downloading and Deploying Translations

Step 1: Download from POEditor

Download completed translations:

npm run locale:download

This downloads translated .po files to src/locale/textdomain/*/LC_MESSAGES/messages.po.

Step 2: Verify Downloads

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.po

Step 3: Generate Runtime Files

The download process automatically generates:

  • Compiled Gettext files (.mo): For PHP runtime
  • JavaScript locale files: For browser internationalization
  • JSON mappings: For key-value lookups

Step 4: Test Translations

  1. Change system locale:

    • Go to Settings → Localization
    • Select a translated language
    • Verify UI displays in chosen language
  2. Check specific terms:

    • Navigate to areas where you added new terms
    • Confirm translations appear correctly
    • Test plural forms if applicable

Translation Status Monitoring

Monitor translation completeness:

# Generate audit report
npm run locale:audit

# View completion status
cat locale/poeditor-audit.md

Troubleshooting

Common Issues

1. Terms Not Extracted

Problem: 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

2. POEditor Upload Fails

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

3. Translations Not Appearing

Problem: UI still shows English after downloading translations

Solutions:

  • Verify .mo files 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

4. Duplicate Terms

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');

5. Term Extraction Errors

Problem: Extraction scripts fail or miss terms

Solutions:

  • Install required dependencies: npm install
  • Ensure xgettext is installed: which xgettext
  • Check file permissions in locale/ directory
  • Run individual extractors to isolate issues

Debug Commands

# 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

Getting Help

  1. Documentation: Review locale/README.md for technical details
  2. Translation Status: Run npm run locale:audit for completeness reports
  3. Community: Check ChurchCRM forums and GitHub issues
  4. POEditor Support: Use POEditor documentation for platform-specific issues

Best Practices Summary

  1. Always extract before uploading: Run npm run locale:term-extract
  2. Use descriptive strings: Avoid abbreviations and technical jargon
  3. Test thoroughly: Verify translations in actual UI contexts
  4. Keep terms complete: Don't fragment sentences
  5. Monitor regularly: Check translation completeness with audit reports
  6. Document context: Add comments for ambiguous terms
  7. 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.

Clone this wiki locally