processwire-multi-language
What I Do
I provide comprehensive guidance for ProcessWire multi-language support:
- Multi-language fields (text, textarea, title)
- Language-alternate fields
- Multi-language URLs and page names
- Code internationalization (i18n)
- Translation functions (
__(),_n(),_x()) - Language switching
- Language packs for admin
When to Use Me
Use this skill when:
- Building multi-language websites
- Setting up multi-language fields
- Creating language switchers
- Translating template code strings
- Working with language-specific URLs
- Accessing field values in specific languages
Getting Started
Required Modules
Install from Modules > Language:
| Module | Purpose |
|---|---|
LanguageSupport | Base language support |
LanguageSupportFields | Multi-language fields |
LanguageSupportPageNames | Multi-language URLs |
Adding Languages
- Go to Setup > Languages
- Click “Add New”
- Enter language name (e.g., “spanish”, “german”)
- Save
Multi-Language Fields
Available Field Types
| Field Type | Description |
|---|---|
TextLanguage | Single line text |
TextareaLanguage | Multi-line text/rich text |
PageTitleLanguage | Page titles |
Converting Existing Fields
- Go to Setup > Fields > [your field]
- Change Type to multi-language version (e.g., Textarea → TextareaLanguage)
- Confirm schema change
- Save
Using Multi-Language Fields
// Output uses current user's language automatically
echo $page->title; // Shows title in user's language
echo $page->body; // Shows body in user's language
// If language version is empty, default language is used
Getting Specific Language Values
// Save current language
$savedLanguage = $user->language;
// Switch to French
$user->language = $languages->get('french');
echo $page->body; // French version
// Restore language
$user->language = $savedLanguage;
Alternative: Using getLanguageValue()
$page->of(false); // Turn off output formatting
$french = $languages->get('french');
$frenchBody = $page->body->getLanguageValue($french);
Setting Language Values
$page->of(false);
// Set for current user's language
$page->body = "Welcome friends";
// Set for specific language
$dutch = $languages->get('dutch');
$spanish = $languages->get('spanish');
$page->body->setLanguageValue($dutch, "Welkom vrienden");
$page->body->setLanguageValue($spanish, "Bienvenidos amigos");
$page->save();
Language-Alternate Fields
Separate fields that substitute for each other based on language.
Naming Convention
{fieldname}_{languagename}
Examples:
body_dutch- Dutch version of bodysummary_spanish- Spanish version of summary
How It Works
// If user language is Dutch and body_dutch exists:
echo $page->body; // Returns body_dutch value
// If body_dutch is empty:
echo $page->body; // Returns default body value
// Direct access always works:
echo $page->body_dutch; // Always returns Dutch version
Setting Values
$page->of(false);
$page->body = "Welcome friends"; // Default language
$page->body_dutch = "Welkom vrienden"; // Dutch
$page->body_spanish = "Bienvenidos amigos"; // Spanish
$page->save();
Multi-Language URLs
Setup
- Install
LanguageSupportPageNamesmodule - Edit homepage > Settings tab
- Set URL Name for each language (e.g., “en”, “es”, “de”)
- Check “Active” for each language
- Save
How It Works
| Language | Homepage URL | Page URL |
|---|---|---|
| Default | / | /about/ |
| Spanish | /es/ | /es/acerca/ |
| German | /de/ | /de/uber-uns/ |
Page Name Translation
Edit any page > Settings tab:
- Set alternative names for each language
- If left blank, default name is used
- Check “Active” to enable language
API Methods
// Get URL in specific language
$url = $page->localUrl($language);
// Get page name in specific language
$name = $page->localName($language);
// Get path in specific language
$path = $page->localPath($language);
// Check if page is viewable in language
if($page->viewable($language)) {
// Page is active in this language
}
Language Switching
Language Switcher (Links)
<?php
$savedLanguage = $user->language;
foreach($languages as $language) {
// Skip current language
if($language->id == $savedLanguage->id) continue;
// Skip if page not active in this language
if(!$page->viewable($language)) continue;
// Switch to get language-specific URL
$user->language = $language;
echo "<a href='$page->url'>$language->title</a>";
}
// Restore language
$user->language = $savedLanguage;
?>
Language Switcher (Select)
<select onchange="window.location=this.value">
<?php foreach($languages as $language): ?>
<?php if(!$page->viewable($language)) continue; ?>
<option
value="<?=$page->localUrl($language)?>"
<?=$user->language->id == $language->id ? 'selected' : ''?>
>
<?=$language->title?>
</option>
<?php endforeach; ?>
</select>
Setting Language Manually
// In _init.php or template
if($input->get->lang) {
$lang = $sanitizer->pageName($input->get->lang);
$language = $languages->get($lang);
if($language->id) {
$user->language = $language;
}
}
Code Internationalization (i18n)
Translate static text in template files.
Translation Functions
| Function | Usage |
|---|---|
__('text') | Basic translation |
_n('single', 'plural', $count) | Plurals |
_x('text', 'context') | Context-specific |
Basic Translation
// Outside class (template files)
echo __('Welcome to our site');
// Inside class (modules)
echo $this->_('Welcome to our site');
With Placeholders
// WRONG - variable not translatable
echo __("Created $count pages");
// RIGHT - use sprintf
echo sprintf(__('Created %d pages'), $count);
// Multiple placeholders with argument swapping
echo sprintf(
__('Your city is %1$s, and your zip is %2$s.'),
$city,
$zipcode
);
Plurals
echo sprintf(
_n('Created %d page', 'Created %d pages', $count),
$count
);
// In class
echo sprintf(
$this->_n('Created %d page', 'Created %d pages', $count),
$count
);
Context
When same word has different meanings:
// As a noun
echo _x('Comment', 'noun');
// As a verb
echo _x('Comment', 'verb');
// In class
echo $this->_x('Post', 'noun');
echo $this->_x('Post', 'verb');
Comments for Translators
// Comment appears to translator
echo __('g:i:s a'); // Date format in PHP date() format
// With notes (secondary comment)
echo __('Welcome Guest'); // Headline // Keep it short
Translation Rules
- One line: Function call must be on single line
- One pair of quotes: No string concatenation inside function
- One call per line: Only one translation function per line
// WRONG
echo __('Hello ' . 'World'); // Concatenation
echo __('Hello') . __('World'); // Two calls per line
// RIGHT
echo __('Hello World');
echo __('Hello') . ' ' .
__('World'); // Separate lines OK
Translating Template Files
Setup Translation
- Go to Setup > Languages > [language]
- Click “Translate File”
- Select your template file
- Enter translations for each string
- Save
Finding Translatable Files
ProcessWire scans for __() calls in:
/site/templates/*.php- Module files
- Core files
Common Patterns
Multi-Language Navigation
<?php foreach($pages->get('/')->children as $item): ?>
<a href="<?=$item->url?>"><?=$item->title?></a>
<?php endforeach; ?>
Title and URL automatically use current language.
Multi-Language Search
$q = $sanitizer->selectorValue($input->get->q);
$results = $pages->find("title|body~=$q");
Searches current language AND default language.
Check Language Availability
// Check if content exists in current language
$french = $languages->get('french');
$page->of(false);
$frenchTitle = $page->title->getLanguageValue($french);
if($frenchTitle) {
echo "French version available";
}
Redirect to User’s Browser Language
// In _init.php (only for homepage)
if($page->id == 1 && !$session->get('lang_set')) {
$browserLang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
$language = $languages->get($browserLang);
if($language->id && $language->id != $languages->getDefault()->id) {
$session->set('lang_set', 1);
$session->redirect($page->localUrl($language));
}
$session->set('lang_set', 1);
}
Hreflang Tags for SEO
<head>
<?php foreach($languages as $language): ?>
<?php if(!$page->viewable($language)) continue; ?>
<link rel="alternate"
hreflang="<?=$language->name?>"
href="<?=$page->localHttpUrl($language)?>">
<?php endforeach; ?>
</head>
Language Permissions
Control who can edit which languages.
Available Permissions
| Permission | Description |
|---|---|
page-edit-lang-default | Edit default language |
page-edit-lang-[name] | Edit specific language |
page-edit-lang-none | Edit non-language fields |
lang-edit | Access language tools |
Setup Translator Role
- Create permission
page-edit-lang-spanish - Create role “translator-spanish”
- Assign permissions:
page-editpage-edit-lang-spanish
- Assign role to templates user should translate
Admin Language Packs
Installing Language Packs
- Download language pack from ProcessWire site
- Go to Setup > Languages > [language]
- Upload pack files
- Save
Finding Language Packs
Community language packs available at:
- ProcessWire modules directory
- ProcessWire forums
Pitfalls / Gotchas
-
Output formatting matters: Language substitution only works with output formatting ON. Use
getLanguageValue()when OFF. -
Empty values fallback: If language value is empty, default language is returned automatically.
-
URL language detection: With
LanguageSupportPageNames, language is auto-detected from URL - no manual setting needed. -
Translation function rules:
- One call per line
- No string concatenation inside
__() - Use sprintf for variables
-
Page active in language: Pages must have “Active” checked for each language in Settings tab.
-
Searching multi-language fields: Searches match current language OR default language.
-
Homepage URL: Default language homepage is always
/, regardless of name setting. -
Module translations: Use
$this->_()inside classes for better performance. -
Context translations: Use
_x()when same text appears multiple times with different meanings. -
Language names: Use simple names like “spanish”, “german” - they become field suffixes.